From 1e9199bd2b49d064f94d440c2a4ccbb39b4f3f7e Mon Sep 17 00:00:00 2001 From: Tatiana Tochko <70381117+TatianaTochko@users.noreply.github.com> Date: Thu, 23 Dec 2021 02:05:28 +0300 Subject: [PATCH] Add ability to import automated tests to Zephyr Add ability to import automated tests to Zephyr: add level properties for story and scenario add ability to create jira issue add ability to link requirement to test --- .../integrations/assets/images/zephyr.png | Bin 0 -> 11097 bytes .../integrations/pages/zephyr-exporter.adoc | 28 +++ .../java/org/vividus/model/jbehave/Story.java | 75 ++++++++ vividus-to-zephyr-exporter/build.gradle | 1 + .../VividusToZephyrExporterApplication.java | 9 +- .../configuration/JiraFieldsMapping.java | 47 +++++ .../ZephyrExporterProperties.java | 13 ++ .../CucumberStoryScenarioConverter.java | 43 +++++ .../databind/AbstractTestCaseSerializer.java | 98 +++++++++++ .../zephyr/databind/TestCaseDeserializer.java | 12 +- .../zephyr/databind/TestStepsSerializer.java | 49 ++++++ .../zephyr/exporter/ZephyrExporter.java | 163 ++++++++++++++++-- .../vividus/zephyr/facade/IZephyrFacade.java | 7 + .../zephyr/facade/TestCaseParameters.java | 59 +++++++ .../vividus/zephyr/facade/ZephyrFacade.java | 76 ++++++-- .../zephyr/model/CucumberTestStep.java | 37 ++++ .../{TestCase.java => TestCaseExecution.java} | 8 +- .../org/vividus/zephyr/model/TestLevel.java | 23 +++ .../vividus/zephyr/model/ZephyrTestCase.java | 68 ++++++++ .../vividus/zephyr/parser/TestCaseParser.java | 43 ++--- .../databind/TestCaseDeserializerTests.java | 22 +-- .../databind/TestStepsSerializerTests.java | 87 ++++++++++ .../zephyr/exporter/ZephyrExporterTests.java | 85 ++++++++- .../zephyr/facade/ZephyrFacadeTests.java | 73 +++++++- .../zephyr/parser/TestCaseParserTests.java | 15 +- .../org/vividus/zephyr/databind/report.json | 28 +++ .../exporter/createreports/test-report.json | 36 ++++ .../test-report-with-testcaseid.json | 67 +++++++ 28 files changed, 1185 insertions(+), 87 deletions(-) create mode 100644 docs/modules/integrations/assets/images/zephyr.png create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/JiraFieldsMapping.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/convertor/CucumberStoryScenarioConverter.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/AbstractTestCaseSerializer.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestStepsSerializer.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/TestCaseParameters.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/CucumberTestStep.java rename vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/{TestCase.java => TestCaseExecution.java} (85%) create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestLevel.java create mode 100644 vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/ZephyrTestCase.java create mode 100644 vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestStepsSerializerTests.java create mode 100644 vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/databind/report.json create mode 100644 vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/createreports/test-report.json create mode 100644 vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/updatereports/test-report-with-testcaseid.json diff --git a/docs/modules/integrations/assets/images/zephyr.png b/docs/modules/integrations/assets/images/zephyr.png new file mode 100644 index 0000000000000000000000000000000000000000..79a687b23b15fcedba331c2d4ac30d3a07f1332a GIT binary patch literal 11097 zcmd6Nbx>U0wP&;){OqrsZs0fGl?P}FDpjr}Kx6B5!}=YIzBk}1#~ z35n@YMnd$9yWYX#8=WsxYiP&%=;%opwpsOSY-|nTbR)Q2@(ynjCt2j>##H3wO^wO{4-Fw`FQ=nc`B+O#f*%))2lNH@G4}b@ zB)-c;-bbJ77^~nwpPlcMrKa{IfCSaN-W7B^>>KZ5)T(~{GBtz&$9NWtT5B?ziM>|z zqsqC1wXi#dOl3q2qkYL1hQ?ERrGLn26N@&2&(N^s==M)P*GI;!waOSsc^kXbw|+78 zW~o)#m^v0AAEK+ir8jxZU!=A)Zado)#lJYlTqynqU-PAYtvNv*b3jCEPAV6_g$l2Z z;``c#Z142lzF5zFRTI<2OCCljZNWMi8Hc}rq2)y&Uow$z7?Evi+!#uor<=ra8 zZsn*xnr%J@CQ#_SXj3#i*_SatG03|JjSz&zZ) z{7{oPk2bn6k8xU6zBTS!^g6jd`_2CV;uG+_rHFbkOj+t|JD(p1xKr!f`LTo3`OelNnsLeZ*V{NR{5f2;a81!lVrp)pjZhUo zYw=>)HCY5+b9j?Ech>ubY`-aIf`@U7^FRbV~pO>m{k9M=a=V8Xev@+#X2U+gii}0_x92yz% z)|ph7BgCwxfF|R$5$m3MY|1-8k_s{p`S_ED`W79&&B<(=JX$FL0m z9PEH*lEG!$2ATDmBOTLic!9^L81bs&?`@=Zay|?#8qn+=3`nW<x<|bc{E)aB@@e6Q_R$4vk?0{Z_|O&ixgNfePH8yj;2{%Y$Bv{*KVJTB zrhC+`rB&>gQQHBZj%+;YJBRS>y4D9bEPcJVg!x<8yq^V&%Ag$ZOt{^E$xPJwMu#@0 zMXD@^PNSprF#h*lcxyYum*TaA(m>V_q6?BI4HL%b^gC2-i7QcW5-BfI#iNU&38plH z0VCU$xBWRP4gOlIWJFOJ5q^hWDFOczWL@#%;7@?6m+k*8}KWAlj^;e z?^Or`@Q;fohNpqA(0#?ZvUa=@ic3@LHy7GayoJt; ztUi*rvAwWbzB+1yALcTG?F9vOL}PSU%GI^efe&79Y?%7hSVDA#flghjez#?-?ZY48 z*W{=8Q_w`ne4Un3>m&7cC zP?t$+T3oC`e^{G&_gY2=;p+xr=T1~yX|f4OO&R4^R&^MD43J?%26-to`K2D|JpLJejFF95ovz7 z&ORP<6r}a05b$V?w7msKh`k)a0qwQhG^o=~7SY{Kebq%4uW6oW8=M7kuwIM(NUs+4Y;an@nxxtQq6&2yi0NX3h&Ws6Ue9a-JPjoP4t7 z6+s^UMqal4v4bM*`T>RdZDdDsdi*!RdP@9a#jE`c$)O&0C}P*-gTM&!RK!&`;KzAS z&PC2hGiI6!n^D@W3;v%qp@NYD8c8(u$CvmXuw(-9{7pPhXg{r zK~A}{mMv1wRVf74bF8fq2oR;)c6L09P5+Q!^@3;dmm&Tp-V zJIgYbQIR3lmc{#{J;zPZ>)CSBcYwO>vl8J{qZPoz`Ayn^xVYF=&3u8BTW7Jl1Y7rAH>&XR#|j}ZtS%4E z;f8zG3-5M-h^f3d6lVBF9VlOx|9-CJ3M?RU`0@h?y@`me=eEkh^Q0pHjbWi42;O^| z1aAkc)KsTcyu0Crudl709Bw;Q#;3LqU(2Pn>D)t^5b8-0${Cew%I5kgs$yv%lft}X z4*UvAAbUMp+xM2S_J*tqHhydeoV4-BS6x61(1xMc=N)Bbbs<}#&biL54EF~D*HW!0 z)`|?08&T;mHE|djLDpRY0m91$I>u(+b6o68BXTn&$M~`eWD)9>J(aKF$)^;Dmn1E`ep&0CH?WrmYB{`KnA^Y7bUg$~U2oV1RV({a43U zQ&HBLWYn4%XW$z~$E6?wCS3qKR%XO^9_+ONN$979izgPX6)h(m*BRG)z0zEW5(eso zzgGEaQubgx&R#g4@=UD}A2#qt)lMFRS)*j?u9(ZvK!n2KB*(;rXtJL%QQ@5+_Nf7c(_({A@LsdL$~f0#L8 z_SXu&FR;b6xzX@@8kjzr%7KN*GQ7Kcp=!U8cyw~0==)B}>Kg%r5{XIb4ODZak%2Cg zQHx==#K~e}E9s&wwMR{!^8K2gw}W#M2VPkS*W6#`*Sd`a0L9^9q05MF)!I4*wxb;!_jc6D`Aq(fc0>}<>BrqtGU)ic0cNIm~d^ZJ8XA1!OgW^X_BpsmdL*+ET#pg`zzC|rJtohFv%ptqF0=|Ent%kL-H@h z!G(F*eD-II=2;rNGd^K!zE29hX4Jz$DA0~FzL+fEQ3dmxOKEc_E%LZ&hbKL1bRvgQ z{b1=#Qj&!)zAB!4wb0v$s^bh!%o0x6Xkxj8DR1xK0lsG4w)B1!E^KkmL9?2(u~isX zue68VW8Faz4;I_jn`5(O&!gbw3nY*zqH84?Yhsk`Uw?P9AmyF?prlsFzv=l<(_m^rJ}8 z$X3LCRTpi@_0I|Yb(cE-Dwve$(YFw=QsrA9pR^L;u5GuK1WpjoEN|;ZnA0fo)C731 z!m$|chJ&}QE$~Ty;_Ri>5mTJnu{gV980p6k;2YU<&63he>OmHRJppK2Lp^TO8C_XW z-3hngB+YFjd2Ib~i*TSpJBrAcX;lr3w9K~7E#hX7(E7(Cn<71nCP9stP8i75r^l`J?!r+6kL6) zn!+%C6+J-QEh(M;Y!pOaB5OV3tGmK{$Oqf4Or}syn>(YV9ZkZc#a|=G7YA%ftWm*# zyRJ=lLGJRNSd_P>D)wBPl(GV5x0`?P?%-#dRwG0t*HZeB{{4oa3n$Xa@gd=B??Gu2 z8{VNAXBoE1wAQe-^a+2mInmRH9y1Wt1+gf@tLSMxsaat`2PuLQ2z|i3S8I2UHU&rr z$I=ErSe@pl`AD?^Vc1l+-(g<4**pXS6}GB%V-vzwvrJ)Se;**A5tiK-(|29ZReloJ zcl|e5q^3PFywaHvka!=dew2rrupMO&s{m^0&j#v81YmL3#i*3|YAIp34j+>(AV*~< zEKdiUm?d_V$(`xB)AJMtiXFRW)0GA$!+5X4vLO1ZN*IAkE#mkGDC<-a!Yp%pkB2Yj zetIp=jkv;)A8lmUqFA=V2z!_EoRk?LE$VZvR!7;OLZ<@F^{+|%JG3hWBPSgNRu0^B zTNrp_bF+VT)J^qk$pkfkn3ga?k*Abf(RHcWts#G{(WRy(*ywqh5GC7FBnIuGGP4U8 zDv+tr=Eo@vF^lS&D|NA z$rfsEb5=K3*-|)Z@U4embc04kO7>P}Ixq_7>Pu%kER8iF7$JqPjTa;o;Im6ktc&)> z4K8iYvlBiJWasVQl8?H!hP6Itw7%`Fy3_EtC700r+Gw7z<5t4*tA44oJG?R^ws887 z*xqc>t6#I)?)t!v$fS7&O^&H4CinM1bL z%;&fqcns+ITK>YlRA!TSU!{S?RxcukHcKi%Dmh9mOTh9=v#k&%6R&vH%)zy(TRdWO z*f$=>>RoX=*!x_nF3mtjy?KQiIXZWm-yGpdWJHiPcJP()C7%P4H^O^V;6y9-S0gnW<%t%Cr)LjrBA?cmmU^dDhh2`1LG_fX7RDzLeQZKjS6NkbbmoYQ@la&0w*?cOpP;Yt5*x0AAB5rehs z5e|{ez}nMwxMW++YpaQ74`6L>F3ltUbrB0>APl@$=BK;~vxSBqpA->ul(-Q>pkI0R zwpHwnm)={u0_pFHCdiZd@agBHdyDyPr~#WWM8l$<;a^vj;n{G@+>P$xg^0T$h8sR_ zFh7~F{E<@+cn^>Ya?OU@1J%(& zdT)mJ1&QkI@ixBzR_XfEdyJItLfO+#_m;vM<&?M>8AJdsu0f#7i>na zhsd=kcG&R{RJ-pN^QH~QD`iVt}V~>hW?d6m$W%na_6UcUT!2+XT*6eDn+=*4lSX%Q{-O1g3$;Sc( z2ue*>O^7mzdQaxR|6Yb3xXRPGjs z?7LT@;Uch-A#LUxD%xO_+JWZG+iHv`Ved%&90v_t3xs?kxf9W<0->^ef?9zwUx>Nn z$nCvINO*MDF;rZsC?{1XR_dF_jtZ@+_<|>Y8g}>wX_Kh)Tt|%;<73mR-FhIkm*9sV z@POg6OFy&Syr+FcXitiaQ~5)&b*^=!L(I3dlM-US)or`0$%wqY6G-_hn-a!YPyu;)1Jq%AM} zWo}!YxAPf)dXzyv;6Zp70bey9b@dh%JU}S4N%y*wwk5AUTTVVOX2Iw-$lSWKOwEoV zAwL!sl7qxdeZ6}ES-a?$)4AF3*JrKVy8j?stS5G#?#M;aEDd90d7>I*1v(%ch#!MD zmJfPPw2Oe;X?wN3+Aqg@y3Je$L)d3(&ZZ3%s~<(Jdb9C=HLnDNmw3)$qtV*aZezP^ zLMTAqCi<-DJqvM^_0IC`&`^Lb+cx{4ys~t2#;g8pl)#e@rCE5vp4uhI;L~!yqRi@r2nQSd;f? zL7j82aO3b?uzN(po}cGR|I6Y%!%|alW`Oj$&W08@oPX(R{9yO-JvG4SAEOt7@oy!% z!BCsCqQrT92vN;zOM2!vEIy5tO?-LkWhJGnv;3H~sz1_LlFou1mU5nYDJ% z^4p@=n2nI?qGsxSNK#T-8LJVOzGgs))X=i_m(RpLlweazI0#1DA3Rf;FXXlwOEj5+ zzH~6|uVr88pY5rHuWSo~KQYEqR~X(ia9hq%lwNqRBC3JSQZ_wBC=KLS4we_>&D)Fk zwsVfto{P#;USzFp*48CWyCs&ZxKi5#!Bi{pdf#Oz+=w4Qr@b4Rc|JyPwd z&dWE3UajV!yXJ})b>)KQoXuLTInzzNy8)(U?7xR6Z9>%>EP&N6X3^pSQaLP_9^@~4 z_*|=TH|R0plE=G*XCRqtp|aqIfxv z1mx)#z$KM))K60H#Ul}*f{N>VfwGA`_2RBRko^tyk5I5xzlaYBqI+d+>>pMiK1!a- zqglLXvE4!^g?OzTG^^X)_<&_}Ss+|?mGZtp2j6$do>6@`Q^&x(l-2Dd$@*GU-Guqk zu50|e9U0U1$XNa@3@hD?f?Fpp94p8v$by|LG5Kh(r}yvr-`Sm?xd z#%-f+;^J7p#R%)NZ{(`VZWw58Z`%eAtQAM#ZQ$_ESAxM_AtwYg*1IFhFL73>dH2H* zl=$U)>&T`@TS|ih%n7|~rzTmB9%jnz>`qeai`~$AhDK>}*<2k^i@+86ua%nin-34` z=9tzhFD`KoSK8MY%{SWcr#F}q_UDL#J{+8Mx)~{fT=cyRMn`%cQw9>tcB7@Rw{J~x z;^Sxx{N5e|X2|lY8{(bTyuUI$mCu&5Dc6PmS9hobUrF+$`MH#}pjIHZGf&)l| z#Ku!g8NYOC)LxpFN$=kshz!e3D^BBK)I!PO3S?-jm@RkR<0impy=PeDZm!VzgtH0` zLtKiAv?X%N+b~&%A26>>wLf(&-?d2||BW*L>kQoD8YV210&)lLr{wscYW(^Iio#e=b(1~ z8~T%*9Fgke;@5(X6t~o98bZrXB#2fG{doIT@1DbN1hXn5bGOM8CySw8JqI!}y>8F} z6VR5QA!(y}Y}^FfD^K2EzeF;0e!(mjCe0#aOKFUkh|&@ybFDN%({OFt9d7Ug@8>q{ z@jMeQBMN13N2=i|X*gf^!b0d{`iqK(()x`i@|2p=Zz?C#_ABikIv3=D%%WPpzHqmq+l& zE}(bwq1L$Ch$j@EsZPGlujBdIL z>t4w3cvtB%i>D48>Oxd6JCtDy47XtaPmRuRwG|eEV7q}W=P51YSm}ESqRPKlJwL;F zX*;sh1O@ICJ0A`3CjBYy^P?rpWCN8sc9wr<@)R|KjbA$Z!$F08uH;W?1eXYY*;fY z(mxtbn$Qrx-J!5-j9tG+Tkoa`r=#x*xCx9ITP#vs+B~$E%GzV2}%_ z9&`Sk+)Y;nBmj$zosEM$&g6L=o}YQ|sRn}LFffON8%a_|1k6~m=84O#&Yh z)7n;OaCT;`PuoSM`FEZ@-0wWrwI&BT&4p=lMnzGS{)pWSLZ(Kh{B}k4>`N*Z?=Dng zBK9fmkzPe;>6Rq>#g zH+Ar6QV(YQFMl25;KHu_f_Jf%X~Wb7Jnvx} zd8?cEW&C-`c8rpBFoxw+yvY|)R-1|Wx^l(TZ5ekd%s1c9;@Lw5(WRlb@kwqS$1Byg z*dRTDhl7>KlMB##2oC6#HZV7x>A}gxki51iQ~MtF6U>-bWpLSc^{~%QQuOidExWe;44qO({iIL0#wSsw3 zeJY=2P#D&gCKQcZuM^4^gZ{{Am|&fIDAOlwkO~)GWjWcuE+xlT{rBzmRK;b{ct>4{ z`W*CQ(}w50udB*=hf)(ZBAG}4LO5}j1JnzkodMgz2ysnf>S6|c^vkQj;wJ@2|J=!} z_)5wEyWImn1<_hP3;qUbD(0R7n00lk@?8q|XL;p+Lfph7zn1TQ+~=F}0aIbNa2h{s zNpqNQm?kTLNa=#}b5VBXbL=^W&+@m}4$SY^oeT`s%TIfX+=M|^&b~_4%!~WUL3cMQkAv5l+odzg&J<=w(k!5;^lS`O**pYBSuxZqK1Y?e;T+Gc29LaG1PXUrVDPx z8H~QDT{JXsFV8DK>!~aWSa~Nm`}W}uADdL1`;WG|Yz-PT^Yvq)i@900Rd%I@(HBGl zmf=9scJ{2X=2Nv14)4)SxQJrLzvE^SW$NTdGCM?cNoD$WrgRzob+JqR3IYqG%6ieV z<4MMd%2{%g4d)7(WDiwGvpJsFHk2}Us1{yjIQr;CeqQgwcA>Agg&B)!2sPYZ`cc;gv)vyZKqG12Y>YS<{cfj5w1_ z@1RFFZLfo$1kV^UGmK9$UnB7#FlGGT&jb&MGi!0p{M!3%>E|TDH~i_xJIZyBTisD(-Xa z$vw?6lQ}(bMv&mm7ZHcq=3n?XWmjI#d3=wjOn6>ddN}cqf1p^HZ2eCVUN)O&<{_s( zL_RwBh=6s*?*QMC65S1Y!cdh^oK#7wd|IBfz`?7V*>V|3EvAMsmkjaAHa^_C^3uS( zisj8P&%_k5DC*4FSmZphxo#r}_zpB$5y1fcaP8tm1Y z$o;jiCpm0lidie3^g|qj>&!f6A50_H^`?XbaIo>?MnTOg*mjr3=1=p#TtUM5BBTbp>lsMiHn)%*9yVfpVYX-2 z*-X+ddc!sbcBB0*TE!YTZ;dkEU~yYTCM)hPJ06bBaz!8ZoJ;XpDXeaw4%zZM(|bga zMz7j70KFdGLmoTFw2o;aiL-(8iO9-C&xyZ9C{uiy^57!X;PbPKXVzMZ2qA$ssHR0qt1KO3a6ULNVGwWsW^AeP?GjtKK9w1d$>y`BtO_{kqo8W@RPaFMaV8Y=jciiO$ zeI`xNW5q~H2GXPOKkCnNlYdE_@n2(8jL-a@&;;CBpUn4(LZ9WZNHUTN5~X4W{{Ia+ C#!S%w literal 0 HcmV?d00001 diff --git a/docs/modules/integrations/pages/zephyr-exporter.adoc b/docs/modules/integrations/pages/zephyr-exporter.adoc index 549c08d9e4..398f151c0b 100644 --- a/docs/modules/integrations/pages/zephyr-exporter.adoc +++ b/docs/modules/integrations/pages/zephyr-exporter.adoc @@ -18,6 +18,10 @@ include::partial$jira-configuration.adoc[] |Required |Description +|`zephyr.exporter.level` +|true +|Property to export stories on STORY or SCENARIO level + |`zephyr.exporter.jira-instance-key` |false |The key of the configured JIRA instance, in case of missing value it will be evaluated automatically based on issue keys being exported @@ -52,6 +56,30 @@ include::partial$jira-configuration.adoc[] |=== +== Jira Fields Mapping + +The Zephyr is a Jira plugin that uses custom Jira fields for it's data, one of the ways to find out custom field names for particular field used by Zephyr on Jira UI (if access to Jira configuration is prohibited) is to request description of some issue. + +=== Test Case Properties + +image::zephyr.png[Zephyr test case view] + +[cols="1,2", options="header"] +|=== + +|Property +|Description + +|`jira.fields-mapping.story-type` +|Key of a field containing story type + +|`jira.fields-mapping.test-step` +|Key of a field containing step of cucumber story/scenario + +|=== + +include::partial$authentication.adoc[] + == Zephyr Execution Status Mapping The Zephyr plugin for Jira has own configurable execution statuses. testExecutionStatus endpoint is used to get the detailed information about the statuses, like: https://jira.example.com/rest/zapi/latest/util/testExecutionStatus. The following properties are used to setup a mapping between Vividus and Zephyr execution statuses. diff --git a/vividus-engine/src/main/java/org/vividus/model/jbehave/Story.java b/vividus-engine/src/main/java/org/vividus/model/jbehave/Story.java index 71167925b9..1b11387398 100644 --- a/vividus-engine/src/main/java/org/vividus/model/jbehave/Story.java +++ b/vividus-engine/src/main/java/org/vividus/model/jbehave/Story.java @@ -16,19 +16,27 @@ package org.vividus.model.jbehave; +import static org.vividus.model.MetaWrapper.META_VALUES_SEPARATOR; + +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; + public class Story { private String path; private Lifecycle lifecycle; private List scenarios; + private List meta; public String getPath() { @@ -60,6 +68,16 @@ public void setScenarios(List scenarios) this.scenarios = scenarios; } + public List getMeta() + { + return meta; + } + + public void setMeta(List meta) + { + this.meta = meta; + } + /** * Get unique scenarios. * @@ -116,4 +134,61 @@ private static void adjustScenarioWithLifecycle(Parameters scenarioParameters, P scenarioParameters.setValues(adjustedValues); } + + /** + * Get unique meta value + * + *

If the meta does not exist or has no value, an empty {@link Optional} will be returned + * + *

Notes + *

    + *
  • meta value is trimmed upon returning
  • + *
  • ; char is used as a separator for meta with multiple values
  • + *
+ * + * @param metaName the meta name + * @return the meta value + * @throws NotUniqueMetaValueException if the meta has more than one value + */ + public Optional getUniqueMetaValue(String metaName) throws NotUniqueMetaValueException + { + Set values = getMetaValues(metaName); + if (values.size() > 1) + { + throw new NotUniqueMetaValueException(metaName, values); + } + return values.isEmpty() ? Optional.empty() : Optional.of(values.iterator().next()); + } + + /** + * Get all meta values + * + *

Notes + *

    + *
  • metas without value are ignored
  • + *
  • meta values are trimmed upon returning
  • + *
  • ; char is used as a separator for meta with multiple values
  • + *
+ * + * @param metaName the meta name + * @return the meta values + */ + public Set getMetaValues(String metaName) + { + return getMetaStream().filter(m -> metaName.equals(m.getName())) + .map(Meta::getValue) + .filter(StringUtils::isNotEmpty) + .map(String::trim) + .map(value -> StringUtils.splitPreserveAllTokens(value, META_VALUES_SEPARATOR)) + .flatMap(Stream::of) + .map(String::trim) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private Stream getMetaStream() + { + return Optional.ofNullable(getMeta()) + .map(List::stream) + .orElseGet(Stream::empty); + } } diff --git a/vividus-to-zephyr-exporter/build.gradle b/vividus-to-zephyr-exporter/build.gradle index 42480372c0..f01ae64868 100644 --- a/vividus-to-zephyr-exporter/build.gradle +++ b/vividus-to-zephyr-exporter/build.gradle @@ -14,6 +14,7 @@ project.description = 'Vividus to Zephyr exporter' dependencies { implementation project(':vividus-util') + implementation project(':vividus-engine') implementation project(':vividus-facade-jira') // https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/257 diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/VividusToZephyrExporterApplication.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/VividusToZephyrExporterApplication.java index f658314a0f..59b4f6f73c 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/VividusToZephyrExporterApplication.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/VividusToZephyrExporterApplication.java @@ -25,8 +25,10 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ImportResource; +import org.springframework.context.annotation.PropertySource; import org.vividus.jira.JiraConfigurationException; import org.vividus.util.property.PropertyParser; +import org.vividus.zephyr.configuration.JiraFieldsMapping; import org.vividus.zephyr.configuration.ZephyrExporterConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterProperties; import org.vividus.zephyr.exporter.ZephyrExporter; @@ -34,7 +36,12 @@ @SpringBootApplication @ImportResource(locations = { "org/vividus/zephyr/spring.xml", "org/vividus/http/client/spring.xml", "org/vividus/jira/spring.xml" }) -@EnableConfigurationProperties({ ZephyrExporterConfiguration.class, ZephyrExporterProperties.class }) +@PropertySource({ + "org/vividus/http/client/defaults.properties", + "org/vividus/util/defaults.properties" +}) +@EnableConfigurationProperties({ ZephyrExporterConfiguration.class, ZephyrExporterProperties.class, + JiraFieldsMapping.class }) public class VividusToZephyrExporterApplication { public static void main(String[] args) throws IOException, JiraConfigurationException diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/JiraFieldsMapping.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/JiraFieldsMapping.java new file mode 100644 index 0000000000..e9c93f9dcc --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/JiraFieldsMapping.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("jira.fields-mapping") +public class JiraFieldsMapping +{ + private String storyType; + + private String testSteps; + + public String getStoryType() + { + return storyType; + } + + public void setStoryType(String storyType) + { + this.storyType = storyType; + } + + public String getTestSteps() + { + return testSteps; + } + + public void setTestSteps(String testSteps) + { + this.testSteps = testSteps; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/ZephyrExporterProperties.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/ZephyrExporterProperties.java index 82b14bd3ec..c17d39ba04 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/ZephyrExporterProperties.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/configuration/ZephyrExporterProperties.java @@ -23,6 +23,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.vividus.zephyr.model.TestCaseStatus; +import org.vividus.zephyr.model.TestLevel; @ConfigurationProperties("zephyr.exporter") public class ZephyrExporterProperties @@ -36,6 +37,8 @@ public class ZephyrExporterProperties private List statusesOfTestCasesToAddToExecution; + private TestLevel level; + public String getJiraInstanceKey() { return jiraInstanceKey; @@ -75,4 +78,14 @@ public void setStatusesOfTestCasesToAddToExecution(List statuses { this.statusesOfTestCasesToAddToExecution = statusesOfTestCasesToAddToExecution; } + + public TestLevel getLevel() + { + return level; + } + + public void setLevel(TestLevel level) + { + this.level = level; + } } diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/convertor/CucumberStoryScenarioConverter.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/convertor/CucumberStoryScenarioConverter.java new file mode 100644 index 0000000000..4a4cc984e7 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/convertor/CucumberStoryScenarioConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.convertor; + +import java.util.ArrayList; +import java.util.List; + +import org.vividus.model.jbehave.Step; +import org.vividus.model.jbehave.Story; +import org.vividus.zephyr.model.CucumberTestStep; + +public final class CucumberStoryScenarioConverter +{ + private CucumberStoryScenarioConverter() + { + } + + public static List convert(String scenarioTitle, List steps) + { + List testSteps = new ArrayList<>(); + return testSteps; + } + + public static List convert(Story steps) + { + List testSteps = new ArrayList<>(); + return testSteps; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/AbstractTestCaseSerializer.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/AbstractTestCaseSerializer.java new file mode 100644 index 0000000000..66165fd7f5 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/AbstractTestCaseSerializer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.databind; + +import java.io.IOException; +import java.util.Collection; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import org.springframework.beans.factory.annotation.Autowired; +import org.vividus.zephyr.configuration.JiraFieldsMapping; +import org.vividus.zephyr.model.ZephyrTestCase; + +public abstract class AbstractTestCaseSerializer extends JsonSerializer +{ + private static final String NAME = "name"; + + @Autowired + private JiraFieldsMapping jiraFieldsMapping; + + @Override + public void serialize(ZephyrTestCase zephyrTestCase, JsonGenerator generator, SerializerProvider serializers) + throws IOException + { + generator.writeStartObject(); + + generator.writeObjectFieldStart("fields"); + + writeObjectWithField(generator, "project", "key", zephyrTestCase.getProjectKey()); + + writeObjectWithField(generator, "issuetype", NAME, "Test"); + + writeJsonArray(generator, "labels", zephyrTestCase.getLabels(), false); + + writeJsonArray(generator, "components", zephyrTestCase.getComponents(), true); + + writeObjectWithField(generator, jiraFieldsMapping.getStoryType(), "value", "Task"); + + writeObjectWithField(generator, "status", NAME, "Backlog"); + + generator.writeEndObject(); + generator.writeEndObject(); + } + + private static void writeJsonArray(JsonGenerator generator, String startField, Collection values, + boolean wrapValuesAsObjects) throws IOException + { + if (values != null) + { + generator.writeArrayFieldStart(startField); + for (String value : values) + { + if (wrapValuesAsObjects) + { + generator.writeStartObject(); + generator.writeStringField(NAME, value); + generator.writeEndObject(); + } + else + { + generator.writeString(value); + } + } + generator.writeEndArray(); + } + } + + protected abstract void serializeCustomFields(T testCase, JsonGenerator generator) throws IOException; + + private static void writeObjectWithField(JsonGenerator generator, String objectKey, String fieldName, + String fieldValue) throws IOException + { + generator.writeObjectFieldStart(objectKey); + generator.writeStringField(fieldName, fieldValue); + generator.writeEndObject(); + } + + protected JiraFieldsMapping getJiraFieldsMapping() + { + return jiraFieldsMapping; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestCaseDeserializer.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestCaseDeserializer.java index 99261d7f7e..d138524f89 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestCaseDeserializer.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestCaseDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,23 +24,23 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.vividus.util.json.JsonPathUtils; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; -public class TestCaseDeserializer extends StdDeserializer +public class TestCaseDeserializer extends StdDeserializer { private static final long serialVersionUID = 7820826665413256040L; public TestCaseDeserializer() { - super(TestCase.class); + super(TestCaseExecution.class); } @Override - public TestCase deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException + public TestCaseExecution deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException { String node = parser.getCodec().readTree(parser).toString(); String status = JsonPathUtils.getData(node, "$.status"); List testCaseIds = JsonPathUtils.getData(node, "$..[?(@.name=='testCaseId')].value"); - return new TestCase(testCaseIds, status); + return new TestCaseExecution(testCaseIds, status); } } diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestStepsSerializer.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestStepsSerializer.java new file mode 100644 index 0000000000..4ec9c7dcf1 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/databind/TestStepsSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.databind; + +import java.io.IOException; +import java.util.List; + +import com.fasterxml.jackson.core.JsonGenerator; + +import org.springframework.stereotype.Component; +import org.vividus.zephyr.model.CucumberTestStep; +import org.vividus.zephyr.model.ZephyrTestCase; + +@Component +public class TestStepsSerializer extends AbstractTestCaseSerializer +{ + @Override + protected void serializeCustomFields(ZephyrTestCase testCase, JsonGenerator generator) throws IOException + { + List steps = testCase.getTestSteps(); + + generator.writeObjectFieldStart(getJiraFieldsMapping().getTestSteps()); + generator.writeArrayFieldStart("tests"); + + for (int i = 0; i < steps.size(); i++) + { + CucumberTestStep testStep = steps.get(i); + generator.writeStartObject(); + generator.writeStringField("Test Step", testStep.getTestStep()); + generator.writeEndObject(); + } + generator.writeEndArray(); + generator.writeEndObject(); + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/exporter/ZephyrExporter.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/exporter/ZephyrExporter.java index dc61e50e8d..c26cee8638 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/exporter/ZephyrExporter.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/exporter/ZephyrExporter.java @@ -16,8 +16,12 @@ package org.vividus.zephyr.exporter; +import static java.lang.System.lineSeparator; + import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.OptionalInt; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -31,24 +35,40 @@ import org.vividus.jira.JiraConfigurationException; import org.vividus.jira.JiraFacade; import org.vividus.jira.model.JiraEntity; +import org.vividus.model.jbehave.NotUniqueMetaValueException; +import org.vividus.model.jbehave.Scenario; +import org.vividus.model.jbehave.Story; +import org.vividus.output.OutputReader; import org.vividus.zephyr.configuration.ZephyrConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterProperties; +import org.vividus.zephyr.convertor.CucumberStoryScenarioConverter; import org.vividus.zephyr.databind.TestCaseDeserializer; import org.vividus.zephyr.facade.IZephyrFacade; +import org.vividus.zephyr.facade.TestCaseParameters; import org.vividus.zephyr.facade.ZephyrFacade; import org.vividus.zephyr.model.ExecutionStatus; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; +import org.vividus.zephyr.model.TestLevel; import org.vividus.zephyr.model.ZephyrExecution; +import org.vividus.zephyr.model.ZephyrTestCase; import org.vividus.zephyr.parser.TestCaseParser; public class ZephyrExporter { + public static final String ZEPHYR_COMPONENTS = "zephyr.components"; + public static final String ZEPHYR_LABELS = "zephyr.labels"; private static final Logger LOGGER = LoggerFactory.getLogger(ZephyrExporter.class); + private static final String TEST_CASE_ID = "testCaseId"; + private static final String STORY = "Story: "; + private static final String ERROR = "Error: "; + private static final String REQUIREMENT_ID = "requirementId"; + + private final List errors = new ArrayList<>(); private final JiraFacade jiraFacade; - private IZephyrFacade zephyrFacade; - private TestCaseParser testCaseParser; - private ZephyrExporterProperties zephyrExporterProperties; + private final IZephyrFacade zephyrFacade; + private final TestCaseParser testCaseParser; + private final ZephyrExporterProperties zephyrExporterProperties; private final ObjectMapper objectMapper; public ZephyrExporter(JiraFacade jiraFacade, ZephyrFacade zephyrFacade, TestCaseParser testCaseParser, @@ -62,24 +82,47 @@ public ZephyrExporter(JiraFacade jiraFacade, ZephyrFacade zephyrFacade, TestCase .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) .build() - .registerModule(new SimpleModule().addDeserializer(TestCase.class, new TestCaseDeserializer())); + .registerModule(new SimpleModule() + .addDeserializer(TestCaseExecution.class, new TestCaseDeserializer())); } public void exportResults() throws IOException, JiraConfigurationException { - List testCasesForImporting = testCaseParser.createTestCases(objectMapper); + for (Story story : OutputReader.readStoriesFromJsons(zephyrExporterProperties.getSourceDirectory())) + { + if (zephyrExporterProperties.getLevel().equals(TestLevel.STORY)) + { + LOGGER.atInfo().addArgument(story::getPath).log("Exporting {} story"); + + exportStory(story); + } + if (zephyrExporterProperties.getLevel().equals(TestLevel.SCENARIO)) + { + LOGGER.atInfo().addArgument(story::getPath).log("Exporting scenarios from {} story"); + for (Scenario scenario : story.getFoldedScenarios()) + { + exportScenario(story.getPath(), scenario); + } + exportTestExecutions(); + } + } + } + + private void exportTestExecutions() throws IOException, JiraConfigurationException + { ZephyrConfiguration configuration = zephyrFacade.prepareConfiguration(); - for (TestCase testCase : testCasesForImporting) + List testCasesForImportingExecution = testCaseParser.createTestCases(objectMapper); + for (TestCaseExecution testCaseExecution : testCasesForImportingExecution) { - exportTestExecution(testCase, configuration); + exportTestExecution(testCaseExecution, configuration); } } - private void exportTestExecution(TestCase testCase, ZephyrConfiguration configuration) + private void exportTestExecution(TestCaseExecution testCaseExecution, ZephyrConfiguration configuration) throws IOException, JiraConfigurationException { - JiraEntity issue = jiraFacade.getIssue(testCase.getKey()); - ZephyrExecution execution = new ZephyrExecution(configuration, issue.getId(), testCase.getStatus()); + JiraEntity issue = jiraFacade.getIssue(testCaseExecution.getKey()); + ZephyrExecution execution = new ZephyrExecution(configuration, issue.getId(), testCaseExecution.getStatus()); OptionalInt executionId; if (zephyrExporterProperties.getUpdateExecutionStatusesOnly()) @@ -99,8 +142,104 @@ private void exportTestExecution(TestCase testCase, ZephyrConfiguration configur } else { - LOGGER.atInfo().addArgument(testCase::getKey).log("Test case result for {} was not exported, " + LOGGER.atInfo().addArgument(testCaseExecution::getKey).log("Test case result for {} was not exported, " + "because execution does not exist"); } } + + private void exportStory(Story story) + { + try + { + String testCaseId = story.getUniqueMetaValue(TEST_CASE_ID).orElse(null); + ZephyrTestCase zephyrTest = new ZephyrTestCase(); + TestCaseParameters parameters = createTestCaseStoryParameters(story); + parameters.setCucumberTestSteps(CucumberStoryScenarioConverter.convert(story)); + fillTestCase(parameters, zephyrTest); + + if (testCaseId != null) + { + zephyrFacade.updateTestCase(testCaseId, zephyrTest); + } + else + { + testCaseId = zephyrFacade.createTestCase(zephyrTest); + } + Optional requirementId = story.getUniqueMetaValue(REQUIREMENT_ID); + createTestsLink(testCaseId, requirementId); + } + catch (IOException | NotUniqueMetaValueException | JiraConfigurationException e) + { + String errorMessage = STORY + story.getPath() + lineSeparator() + ERROR + e.getMessage(); + errors.add(errorMessage); + LOGGER.atError().setCause(e).log("Got an error while exporting story"); + } + } + + private void exportScenario(String storyTitle, Scenario scenario) + { + String scenarioTitle = scenario.getTitle(); + LOGGER.atInfo().addArgument(scenarioTitle).log("Exporting {} scenario"); + + try + { + String testCaseId = scenario.getUniqueMetaValue(TEST_CASE_ID).orElse(null); + + ZephyrTestCase zephyrTest = new ZephyrTestCase(); + TestCaseParameters parameters = createTestCaseScenarioParameters(scenario); + parameters.setCucumberTestSteps(CucumberStoryScenarioConverter + .convert(scenario.getTitle(), scenario.collectSteps())); + fillTestCase(parameters, zephyrTest); + + if (testCaseId != null) + { + zephyrFacade.updateTestCase(testCaseId, zephyrTest); + } + else + { + testCaseId = zephyrFacade.createTestCase(zephyrTest); + } + Optional requirementId = scenario.getUniqueMetaValue(REQUIREMENT_ID); + createTestsLink(testCaseId, requirementId); + } + catch (IOException | NotUniqueMetaValueException | JiraConfigurationException e) + { + String errorMessage = STORY + storyTitle + lineSeparator() + "Scenario: " + scenarioTitle + + lineSeparator() + ERROR + e.getMessage(); + errors.add(errorMessage); + LOGGER.atError().setCause(e).log("Got an error while exporting scenario"); + } + } + + private TestCaseParameters createTestCaseScenarioParameters(Scenario scenario) + { + TestCaseParameters parameters = new TestCaseParameters(); + parameters.setLabels(scenario.getMetaValues(ZEPHYR_LABELS)); + parameters.setComponents(scenario.getMetaValues(ZEPHYR_COMPONENTS)); + return parameters; + } + + private TestCaseParameters createTestCaseStoryParameters(Story story) + { + TestCaseParameters parameters = new TestCaseParameters(); + parameters.setLabels(story.getMetaValues(ZEPHYR_LABELS)); + parameters.setComponents(story.getMetaValues(ZEPHYR_COMPONENTS)); + return parameters; + } + + private void fillTestCase(TestCaseParameters parameters, ZephyrTestCase zephyrTest) + { + zephyrTest.setLabels(parameters.getLabels()); + zephyrTest.setComponents(parameters.getComponents()); + parameters.setCucumberTestSteps(parameters.getCucumberTestSteps()); + } + + private void createTestsLink(String testCaseId, Optional requirementId) + throws IOException, JiraConfigurationException + { + if (requirementId.isPresent()) + { + zephyrFacade.createTestsLink(testCaseId, requirementId.get()); + } + } } diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/IZephyrFacade.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/IZephyrFacade.java index 3a1c797e88..64d2cbbfeb 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/IZephyrFacade.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/IZephyrFacade.java @@ -21,6 +21,7 @@ import org.vividus.jira.JiraConfigurationException; import org.vividus.zephyr.configuration.ZephyrConfiguration; +import org.vividus.zephyr.model.ZephyrTestCase; public interface IZephyrFacade { @@ -28,7 +29,13 @@ public interface IZephyrFacade Integer createExecution(String execution) throws IOException, JiraConfigurationException; + void updateTestCase(String testCaseId, ZephyrTestCase zephyrTest) throws IOException, JiraConfigurationException; + + String createTestCase(ZephyrTestCase zephyrTest) throws IOException, JiraConfigurationException; + void updateExecutionStatus(int executionId, String executionBody) throws IOException, JiraConfigurationException; OptionalInt findExecutionId(String issueId) throws IOException, JiraConfigurationException; + + void createTestsLink(String testCaseId, String requirementId) throws IOException, JiraConfigurationException; } diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/TestCaseParameters.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/TestCaseParameters.java new file mode 100644 index 0000000000..f7819b01d8 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/TestCaseParameters.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.facade; + +import java.util.List; +import java.util.Set; + +import org.vividus.zephyr.model.CucumberTestStep; + +public class TestCaseParameters +{ + private Set labels; + private Set components; + private List cucumberTestSteps; + + public Set getLabels() + { + return labels; + } + + public void setLabels(Set labels) + { + this.labels = labels; + } + + public Set getComponents() + { + return components; + } + + public void setComponents(Set components) + { + this.components = components; + } + + public List getCucumberTestSteps() + { + return cucumberTestSteps; + } + + public void setCucumberTestSteps(List cucumberTestSteps) + { + this.cucumberTestSteps = cucumberTestSteps; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/ZephyrFacade.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/ZephyrFacade.java index 37fc0e267b..40a55ab0bf 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/ZephyrFacade.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/facade/ZephyrFacade.java @@ -27,7 +27,14 @@ import java.util.Optional; import java.util.OptionalInt; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.vividus.jira.JiraClient; import org.vividus.jira.JiraClientProvider; import org.vividus.jira.JiraConfigurationException; @@ -38,24 +45,33 @@ import org.vividus.zephyr.configuration.ZephyrConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterProperties; +import org.vividus.zephyr.databind.TestStepsSerializer; import org.vividus.zephyr.model.TestCaseStatus; +import org.vividus.zephyr.model.ZephyrTestCase; public class ZephyrFacade implements IZephyrFacade { + private static final Logger LOGGER = LoggerFactory.getLogger(ZephyrFacade.class); private static final String ZAPI_ENDPOINT = "/rest/zapi/latest/"; private final JiraFacade jiraFacade; private final JiraClientProvider jiraClientProvider; private final ZephyrExporterConfiguration zephyrExporterConfiguration; private final ZephyrExporterProperties zephyrExporterProperties; + private final ObjectMapper objectMapper; - public ZephyrFacade(JiraFacade jiraFacade, JiraClientProvider jiraClientProvider, - ZephyrExporterConfiguration zephyrExporterConfiguration, ZephyrExporterProperties zephyrExporterProperties) + public ZephyrFacade(JiraFacade jiraFacade, JiraClientProvider jiraClientProvider, ZephyrExporterConfiguration + zephyrExporterConfiguration, ZephyrExporterProperties zephyrExporterProperties, + TestStepsSerializer testStepsSerializer) { this.jiraFacade = jiraFacade; this.jiraClientProvider = jiraClientProvider; this.zephyrExporterConfiguration = zephyrExporterConfiguration; this.zephyrExporterProperties = zephyrExporterProperties; + this.objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .registerModule(new SimpleModule().addSerializer(ZephyrTestCase.class, testStepsSerializer)); } @Override @@ -66,6 +82,25 @@ public Integer createExecution(String execution) throws IOException, JiraConfigu return executionId.get(0); } + @Override + public void updateTestCase(String testCaseId, ZephyrTestCase zephyrTest) + { + LOGGER.atInfo().addArgument(testCaseId).log("Updating Test Case: {}"); + } + + @Override + public String createTestCase(ZephyrTestCase zephyrTest) throws IOException, JiraConfigurationException + { + zephyrTest.setProjectKey(zephyrExporterConfiguration.getProjectKey()); + String createTestRequest = objectMapper.writeValueAsString(zephyrTest); + LOGGER.atInfo().addArgument(createTestRequest).log("Creating Test Case: {}"); + String response = jiraFacade + .createIssue(createTestRequest, Optional.ofNullable(zephyrExporterProperties.getJiraInstanceKey())); + String issueKey = JsonPathUtils.getData(response, "$.key"); + LOGGER.atInfo().addArgument(issueKey).log("Test with key {} has been created"); + return issueKey; + } + @Override public void updateExecutionStatus(int executionId, String executionBody) throws IOException, JiraConfigurationException @@ -144,19 +179,6 @@ private String findFolderId(String cycleId, String projectAndVersionUrlQuery) return folderId.get(0).toString(); } - private Map getExecutionStatuses() throws IOException, JiraConfigurationException - { - String json = getJiraClient().executeGet(ZAPI_ENDPOINT + "util/testExecutionStatus"); - Map testStatusPerZephyrIdMapping = new EnumMap<>(TestCaseStatus.class); - zephyrExporterConfiguration.getStatuses().entrySet().forEach(s -> - { - List statusId = JsonPathUtils.getData(json, String.format("$.[?(@.name=='%s')].id", s.getValue())); - notEmpty(statusId, "Status '%s' does not exist", s.getValue()); - testStatusPerZephyrIdMapping.put(s.getKey(), statusId.get(0)); - }); - return testStatusPerZephyrIdMapping; - } - @Override public OptionalInt findExecutionId(String issueId) throws IOException, JiraConfigurationException { @@ -177,6 +199,30 @@ public OptionalInt findExecutionId(String issueId) throws IOException, JiraConfi return executionId.size() != 0 ? OptionalInt.of(executionId.get(0)) : OptionalInt.empty(); } + @Override + public void createTestsLink(String testCaseId, String requirementId) throws IOException, JiraConfigurationException + { + String linkType = "Tests"; + LOGGER.atInfo().addArgument(linkType) + .addArgument(testCaseId) + .addArgument(requirementId) + .log("Create '{}' link from {} to {}"); + jiraFacade.createIssueLink(testCaseId, requirementId, linkType); + } + + private Map getExecutionStatuses() throws IOException, JiraConfigurationException + { + String json = getJiraClient().executeGet(ZAPI_ENDPOINT + "util/testExecutionStatus"); + Map testStatusPerZephyrIdMapping = new EnumMap<>(TestCaseStatus.class); + zephyrExporterConfiguration.getStatuses().entrySet().forEach(s -> + { + List statusId = JsonPathUtils.getData(json, String.format("$.[?(@.name=='%s')].id", s.getValue())); + notEmpty(statusId, "Status '%s' does not exist", s.getValue()); + testStatusPerZephyrIdMapping.put(s.getKey(), statusId.get(0)); + }); + return testStatusPerZephyrIdMapping; + } + private JiraClient getJiraClient() throws JiraConfigurationException { return jiraClientProvider diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/CucumberTestStep.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/CucumberTestStep.java new file mode 100644 index 0000000000..997024afdc --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/CucumberTestStep.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.model; + +public class CucumberTestStep +{ + private String testStep; + + public CucumberTestStep(String testStep) + { + this.testStep = testStep; + } + + public String getTestStep() + { + return testStep; + } + + public void setTestStep(String testStep) + { + this.testStep = testStep; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCase.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCaseExecution.java similarity index 85% rename from vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCase.java rename to vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCaseExecution.java index eb5a8ff277..407bf8b487 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCase.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestCaseExecution.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,18 @@ import java.util.List; -public class TestCase +public class TestCaseExecution { private List keys; private TestCaseStatus status; - public TestCase(List keys, String status) + public TestCaseExecution(List keys, String status) { this.keys = keys; this.status = TestCaseStatus.valueOf(status.toUpperCase()); } - public TestCase(String key, TestCaseStatus status) + public TestCaseExecution(String key, TestCaseStatus status) { this.keys = List.of(key); this.status = status; diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestLevel.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestLevel.java new file mode 100644 index 0000000000..dbb6124ed8 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/TestLevel.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.model; + +public enum TestLevel +{ + STORY, + SCENARIO; +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/ZephyrTestCase.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/ZephyrTestCase.java new file mode 100644 index 0000000000..d1bedf997f --- /dev/null +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/model/ZephyrTestCase.java @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.model; + +import java.util.List; +import java.util.Set; + +public class ZephyrTestCase +{ + private String projectKey; + private Set labels; + private Set components; + private List testSteps; + + public String getProjectKey() + { + return projectKey; + } + + public void setProjectKey(String projectKey) + { + this.projectKey = projectKey; + } + + public Set getLabels() + { + return labels; + } + + public void setLabels(Set labels) + { + this.labels = labels; + } + + public Set getComponents() + { + return components; + } + + public void setComponents(Set components) + { + this.components = components; + } + + public List getTestSteps() + { + return testSteps; + } + + public void setTestSteps(List testSteps) + { + this.testSteps = testSteps; + } +} diff --git a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/parser/TestCaseParser.java b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/parser/TestCaseParser.java index d555f2d9a4..5c8fa6709a 100644 --- a/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/parser/TestCaseParser.java +++ b/vividus-to-zephyr-exporter/src/main/java/org/vividus/zephyr/parser/TestCaseParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; import org.vividus.zephyr.configuration.ZephyrExporterProperties; import org.vividus.zephyr.configuration.ZephyrFileVisitor; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; import org.vividus.zephyr.model.TestCaseStatus; public class TestCaseParser @@ -47,27 +47,27 @@ public TestCaseParser(ZephyrExporterProperties zephyrExporterProperties) this.zephyrExporterProperties = zephyrExporterProperties; } - public List createTestCases(ObjectMapper objectMapper) throws IOException + public List createTestCases(ObjectMapper objectMapper) throws IOException { - List testCases = parseJsonResultsFile(getJsonResultsFiles(), objectMapper); - notEmpty(testCases, "There are not any test cases for exporting", + List testCaseExecutions = parseJsonResultsFile(getJsonResultsFiles(), objectMapper); + notEmpty(testCaseExecutions, "There are not any test cases for exporting", zephyrExporterProperties.getSourceDirectory()); - LOGGER.info("Test cases: {}", testCases); - Map> testCasesMap = testCases.stream() - .collect(Collectors.groupingBy(TestCase::getKey, - Collectors.mapping(TestCase::getStatus, Collectors.toCollection(TreeSet::new)))); + LOGGER.info("Test cases: {}", testCaseExecutions); + Map> testCasesMap = testCaseExecutions.stream() + .collect(Collectors.groupingBy(TestCaseExecution::getKey, + Collectors.mapping(TestCaseExecution::getStatus, Collectors.toCollection(TreeSet::new)))); - List testCasesForExporting = new ArrayList<>(); + List testCasesForExportingExecution = new ArrayList<>(); for (Map.Entry> entry : testCasesMap.entrySet()) { TestCaseStatus testCaseStatus = entry.getValue().first(); if (zephyrExporterProperties.getStatusesOfTestCasesToAddToExecution().contains(testCaseStatus)) { - testCasesForExporting.add(new TestCase(entry.getKey(), testCaseStatus)); + testCasesForExportingExecution.add(new TestCaseExecution(entry.getKey(), testCaseStatus)); } } - LOGGER.info("Test cases for exporting to JIRA: {}", testCasesForExporting); - return testCasesForExporting; + LOGGER.info("Test cases for exporting to JIRA: {}", testCasesForExportingExecution); + return testCasesForExportingExecution; } private List getJsonResultsFiles() throws IOException @@ -81,15 +81,15 @@ private List getJsonResultsFiles() throws IOException return jsonFiles; } - private List parseJsonResultsFile(List jsonFiles, ObjectMapper objectMapper) + private List parseJsonResultsFile(List jsonFiles, ObjectMapper objectMapper) { - List testCases = new ArrayList<>(); + List testCaseExecutions = new ArrayList<>(); for (File jsonFile : jsonFiles) { try { - TestCase testCase = objectMapper.readValue(jsonFile, TestCase.class); - List testCaseKeys = testCase.getKeys(); + TestCaseExecution testCaseExecution = objectMapper.readValue(jsonFile, TestCaseExecution.class); + List testCaseKeys = testCaseExecution.getKeys(); if (testCaseKeys.isEmpty()) { continue; @@ -98,13 +98,14 @@ private List parseJsonResultsFile(List jsonFiles, ObjectMapper o { for (String key : testCaseKeys) { - TestCase additionalTestCase = new TestCase(key, testCase.getStatus()); - testCases.add(additionalTestCase); + TestCaseExecution additionalTestCaseExecution = new TestCaseExecution(key, + testCaseExecution.getStatus()); + testCaseExecutions.add(additionalTestCaseExecution); } } else { - testCases.add(testCase); + testCaseExecutions.add(testCaseExecution); } } catch (IOException e) @@ -112,6 +113,6 @@ private List parseJsonResultsFile(List jsonFiles, ObjectMapper o throw new IllegalArgumentException("Problem with reading values from json file " + jsonFile, e); } } - return testCases; + return testCaseExecutions; } } diff --git a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestCaseDeserializerTests.java b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestCaseDeserializerTests.java index 936ba44c24..c62fb5bfa6 100644 --- a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestCaseDeserializerTests.java +++ b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestCaseDeserializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; import org.vividus.zephyr.model.TestCaseStatus; @ExtendWith(MockitoExtension.class) @@ -60,10 +60,10 @@ void testDeserialize() throws IOException JsonNode root = MAPPER.readTree("{\"status\" : \"failed\", \"labels\" : [{\"name\" : \"testCaseId\"," + "\"value\" : \"TEST-001\"}, {\"name\" : \"framework\", \"value\" : \"Vividus\"}]}"); when(objectCodec.readTree(parser)).thenReturn(root); - TestCase testCase = deserializer.deserialize(parser, null); + TestCaseExecution testCaseExecution = deserializer.deserialize(parser, null); - assertEquals(List.of("TEST-001"), testCase.getKeys()); - assertEquals(TestCaseStatus.FAILED, testCase.getStatus()); + assertEquals(List.of("TEST-001"), testCaseExecution.getKeys()); + assertEquals(TestCaseStatus.FAILED, testCaseExecution.getStatus()); } @Test @@ -72,10 +72,10 @@ void testDeserializeWithoutTestCaseId() throws IOException JsonNode root = MAPPER.readTree("{\"status\" : \"passed\"," + "\"labels\" : [{\"name\" : \"framework\", \"value\" : \"Vividus\"}]}"); when(objectCodec.readTree(parser)).thenReturn(root); - TestCase testCase = deserializer.deserialize(parser, null); + TestCaseExecution testCaseExecution = deserializer.deserialize(parser, null); - assertEquals(List.of(), testCase.getKeys()); - assertEquals(TestCaseStatus.PASSED, testCase.getStatus()); + assertEquals(List.of(), testCaseExecution.getKeys()); + assertEquals(TestCaseStatus.PASSED, testCaseExecution.getStatus()); } @Test @@ -85,9 +85,9 @@ void testDeserializeWithTwoTestCaseIds() throws IOException + "\"value\" : \"TEST-002\"}, {\"name\" : \"testCaseId\",\"value\" : \"TEST-003\"}," + "{\"name\" : \"framework\", \"value\" : \"Vividus\"}]}"); when(objectCodec.readTree(parser)).thenReturn(root); - TestCase testCase = deserializer.deserialize(parser, null); + TestCaseExecution testCaseExecution = deserializer.deserialize(parser, null); - assertEquals(List.of("TEST-002", "TEST-003"), testCase.getKeys()); - assertEquals(TestCaseStatus.BROKEN, testCase.getStatus()); + assertEquals(List.of("TEST-002", "TEST-003"), testCaseExecution.getKeys()); + assertEquals(TestCaseStatus.BROKEN, testCaseExecution.getStatus()); } } diff --git a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestStepsSerializerTests.java b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestStepsSerializerTests.java new file mode 100644 index 0000000000..474d7c43a9 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/databind/TestStepsSerializerTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vividus.zephyr.databind; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashSet; +import java.util.List; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.vividus.util.ResourceUtils; +import org.vividus.zephyr.configuration.JiraFieldsMapping; +import org.vividus.zephyr.model.CucumberTestStep; +import org.vividus.zephyr.model.ZephyrTestCase; + +@ExtendWith(MockitoExtension.class) +public class TestStepsSerializerTests +{ + @Spy + private JiraFieldsMapping fields; + @InjectMocks + private TestStepsSerializer serializer; + + @BeforeEach + void init() + { + fields.setTestSteps("step 1, step 2"); + fields.setStoryType("story-type"); + } + + @Test + void shouldSerializeTest() throws IOException + { + ZephyrTestCase test = createBaseTest(); + test.setLabels(new LinkedHashSet<>(List.of("label 1", "label 2"))); + test.setComponents(new LinkedHashSet<>(List.of("component 1", "component 2"))); + + try (StringWriter writer = new StringWriter()) + { + JsonFactory factory = new JsonFactory(); + JsonGenerator generator = factory.createGenerator(writer); + + serializer.serialize(test, generator, null); + + generator.close(); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode actual = mapper.readTree(writer.toString()); + JsonNode expected = mapper.readTree(ResourceUtils.loadResource(getClass(), "report.json")); + assertEquals(expected, actual); + } + } + + private static ZephyrTestCase createBaseTest() + { + ZephyrTestCase test = new ZephyrTestCase(); + test.setProjectKey("project key"); + test.setTestSteps(List.of(new CucumberTestStep("testStep 1"), new CucumberTestStep("testStep 2"))); + return test; + } +} diff --git a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/exporter/ZephyrExporterTests.java b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/exporter/ZephyrExporterTests.java index 3f2d3800c3..bd6a645f46 100644 --- a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/exporter/ZephyrExporterTests.java +++ b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/exporter/ZephyrExporterTests.java @@ -18,14 +18,20 @@ import static com.github.valfirst.slf4jtest.LoggingEvent.info; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -37,42 +43,53 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.vividus.jira.JiraConfigurationException; import org.vividus.jira.JiraFacade; import org.vividus.jira.model.JiraEntity; +import org.vividus.util.ResourceUtils; import org.vividus.zephyr.configuration.ZephyrConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterProperties; import org.vividus.zephyr.facade.ZephyrFacade; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; import org.vividus.zephyr.model.TestCaseStatus; +import org.vividus.zephyr.model.TestLevel; import org.vividus.zephyr.parser.TestCaseParser; @ExtendWith({MockitoExtension.class, TestLoggerFactoryExtension.class}) class ZephyrExporterTests { + public static final String CREATEREPORTS = "createreports"; private static final String TEST_CASE_KEY1 = "TEST-1"; private static final String TEST_CASE_KEY2 = "TEST-2"; private static final String ISSUE_ID1 = "1"; private static final String ISSUE_ID2 = "2"; private static final String STATUS_UPDATE_JSON = "{\"status\":\"-1\"}"; + private static final String SCENARIO_TITLE = "scenarioTitle"; + private static final String STORY_TITLE = "storyPath"; + private static final String EXPORTING_SCENARIO = "Exporting {} scenario"; + private static final String EXPORTING_SCENARIO_FROM_STORY = "Exporting scenarios from {} story"; private final TestLogger testLogger = TestLoggerFactory.getTestLogger(ZephyrExporter.class); @Mock private JiraFacade jiraFacade; @Mock private ZephyrFacade zephyrFacade; @Mock private TestCaseParser testCaseParser; - @Mock private ZephyrExporterProperties zephyrExporterProperties; + @Spy private ZephyrExporterProperties zephyrExporterProperties; @InjectMocks private ZephyrExporter zephyrExporter; + private final TestLogger logger = TestLoggerFactory.getTestLogger(ZephyrExporter.class); + @Test void testExportResults() throws IOException, URISyntaxException, JiraConfigurationException { when(testCaseParser.createTestCases(any())).thenReturn(List.of( - new TestCase(TEST_CASE_KEY1, TestCaseStatus.SKIPPED), - new TestCase(TEST_CASE_KEY2, TestCaseStatus.PASSED))); + new TestCaseExecution(TEST_CASE_KEY1, TestCaseStatus.SKIPPED), + new TestCaseExecution(TEST_CASE_KEY2, TestCaseStatus.PASSED))); when(zephyrFacade.prepareConfiguration()).thenReturn(prepareTestConfiguration()); mockJiraIssueRetrieve(TEST_CASE_KEY1, ISSUE_ID1); mockJiraIssueRetrieve(TEST_CASE_KEY2, ISSUE_ID2); @@ -80,6 +97,10 @@ void testExportResults() throws IOException, URISyntaxException, JiraConfigurati + "\"projectId\":\"11111\",\"versionId\":\"11112\"}"; when(zephyrFacade.createExecution(String.format(executionBody, ISSUE_ID1))).thenReturn(111); when(zephyrFacade.createExecution(String.format(executionBody, ISSUE_ID2))).thenReturn(222); + URI jsonResultsUri = getJsonResultsUri(CREATEREPORTS); + + zephyrExporterProperties.setLevel(TestLevel.SCENARIO); + zephyrExporterProperties.setSourceDirectory(Paths.get(jsonResultsUri)); zephyrExporter.exportResults(); verify(zephyrFacade).updateExecutionStatus(111, STATUS_UPDATE_JSON); verify(zephyrFacade).updateExecutionStatus(222, "{\"status\":\"1\"}"); @@ -90,20 +111,61 @@ void testExportResultsWithOnlyStatusUpdate() throws IOException, URISyntaxExcept { when(zephyrExporterProperties.getUpdateExecutionStatusesOnly()).thenReturn(true); when(testCaseParser.createTestCases(any())).thenReturn(List.of( - new TestCase(TEST_CASE_KEY1, TestCaseStatus.SKIPPED), - new TestCase(TEST_CASE_KEY2, TestCaseStatus.PASSED))); + new TestCaseExecution(TEST_CASE_KEY1, TestCaseStatus.SKIPPED), + new TestCaseExecution(TEST_CASE_KEY2, TestCaseStatus.PASSED))); when(zephyrFacade.prepareConfiguration()).thenReturn(prepareTestConfiguration()); mockJiraIssueRetrieve(TEST_CASE_KEY1, ISSUE_ID1); mockJiraIssueRetrieve(TEST_CASE_KEY2, ISSUE_ID2); when(zephyrFacade.findExecutionId(ISSUE_ID1)).thenReturn(OptionalInt.of(111)); when(zephyrFacade.findExecutionId(ISSUE_ID2)).thenReturn(OptionalInt.empty()); + URI jsonResultsUri = getJsonResultsUri("updatereports"); + zephyrExporterProperties.setLevel(TestLevel.SCENARIO); + zephyrExporterProperties.setSourceDirectory(Paths.get(jsonResultsUri)); + zephyrExporter.exportResults(); verify(zephyrFacade).updateExecutionStatus(111, STATUS_UPDATE_JSON); - verifyNoMoreInteractions(zephyrFacade); - assertThat(testLogger.getLoggingEvents(), is(List.of(info("Test case result for {} was not exported, " + assertThat(testLogger.getLoggingEvents(), is(List.of(info(EXPORTING_SCENARIO_FROM_STORY, STORY_TITLE), + info(EXPORTING_SCENARIO, SCENARIO_TITLE), info("Test case result for {} was not exported, " + "because execution does not exist", TEST_CASE_KEY2)))); } + @Test + void shouldFailIfResultsDirectoryIsEmpty(@TempDir Path sourceDirectory) + { + zephyrExporterProperties.setSourceDirectory(sourceDirectory); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + zephyrExporter::exportResults); + + assertEquals(String.format("The directory '%s' does not contain needed JSON files", sourceDirectory), + exception.getMessage()); + assertThat(logger.getLoggingEvents(), empty()); + } + + @Test + void shouldExportNewTestWithStoryLevel() throws URISyntaxException, IOException, JiraConfigurationException + { + URI jsonResultsUri = getJsonResultsUri(CREATEREPORTS); + zephyrExporterProperties.setLevel(TestLevel.STORY); + zephyrExporterProperties.setSourceDirectory(Paths.get(jsonResultsUri)); + + zephyrExporter.exportResults(); + + assertThat(logger.getLoggingEvents(), is(Collections.singletonList(info("Exporting {} story", STORY_TITLE)))); + } + + @Test + void shouldExportNewTestWithScenarioLevel() throws URISyntaxException, IOException, JiraConfigurationException + { + URI jsonResultsUri = getJsonResultsUri(CREATEREPORTS); + zephyrExporterProperties.setLevel(TestLevel.SCENARIO); + zephyrExporterProperties.setSourceDirectory(Paths.get(jsonResultsUri)); + zephyrExporter.exportResults(); + + assertThat(logger.getLoggingEvents(), is(List.of(info(EXPORTING_SCENARIO_FROM_STORY, STORY_TITLE), + info(EXPORTING_SCENARIO, SCENARIO_TITLE)))); + } + private ZephyrConfiguration prepareTestConfiguration() { ZephyrConfiguration configuration = new ZephyrConfiguration(); @@ -124,4 +186,9 @@ private void mockJiraIssueRetrieve(String issueKey, String issueId) throws IOExc issue.setId(issueId); when(jiraFacade.getIssue(issueKey)).thenReturn(issue); } + + public URI getJsonResultsUri(String resource) throws URISyntaxException + { + return ResourceUtils.findResource(getClass(), resource).toURI(); + } } diff --git a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/facade/ZephyrFacadeTests.java b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/facade/ZephyrFacadeTests.java index 517c306b74..ea2c0cf1b4 100644 --- a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/facade/ZephyrFacadeTests.java +++ b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/facade/ZephyrFacadeTests.java @@ -16,19 +16,31 @@ package org.vividus.zephyr.facade; +import static com.github.valfirst.slf4jtest.LoggingEvent.info; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.EnumMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.github.valfirst.slf4jtest.TestLogger; +import com.github.valfirst.slf4jtest.TestLoggerFactory; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,7 +56,10 @@ import org.vividus.zephyr.configuration.ZephyrConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterConfiguration; import org.vividus.zephyr.configuration.ZephyrExporterProperties; +import org.vividus.zephyr.databind.TestStepsSerializer; +import org.vividus.zephyr.model.CucumberTestStep; import org.vividus.zephyr.model.TestCaseStatus; +import org.vividus.zephyr.model.ZephyrTestCase; @ExtendWith(MockitoExtension.class) class ZephyrFacadeTests @@ -66,18 +81,40 @@ class ZephyrFacadeTests private static final String FOLDER_ID = "11114"; private static final String ISSUE_ID = "111"; private static final String TEST = "test"; + private static final String BODY = "{}"; + private static final String CREATE_RESPONSE = "{\"key\" : \"" + ISSUE_ID + "\"}"; + private static final String REQUIREMENT_ID = "requirement id"; + private static final String TESTS = "Tests"; + private static final String CREATE_LINK_FROM_TO = "Create '{}' link from {} to {}"; @Mock private JiraFacade jiraFacade; @Mock private JiraClient client; @Mock private JiraClientProvider jiraClientProvider; @Mock private ZephyrExporterConfiguration zephyrExporterConfiguration; + @Mock private TestStepsSerializer testStepsSerializer; + @Mock private ZephyrExporterProperties zephyrExporterProperties; @InjectMocks private ZephyrFacade zephyrFacade; + private final TestLogger logger = TestLoggerFactory.getTestLogger(ZephyrFacade.class); + @BeforeEach void init() { zephyrFacade = new ZephyrFacade(jiraFacade, jiraClientProvider, zephyrExporterConfiguration, - new ZephyrExporterProperties()); + zephyrExporterProperties, testStepsSerializer); + } + + @Test + void testCreateTestLink() throws IOException, JiraConfigurationException + { + String requirementId = REQUIREMENT_ID; + String linkType = TESTS; + + zephyrFacade.createTestsLink(ISSUE_ID, requirementId); + + verify(jiraFacade).createIssueLink(ISSUE_ID, requirementId, linkType); + assertThat(logger.getLoggingEvents(), + is(List.of(info(CREATE_LINK_FROM_TO, linkType, ISSUE_ID, requirementId)))); } @Test @@ -244,6 +281,20 @@ void testExecutionIdNotFound() throws IOException, JiraConfigurationException assertEquals(OptionalInt.empty(), zephyrFacade.findExecutionId(ISSUE_ID)); } + @Test + void testCreateNewTestCase() throws IOException, JiraConfigurationException + { + ZephyrTestCase test = createZephyrTestCase(); + mockSerialization(test); + when(jiraFacade.createIssue(BODY, Optional.empty())).thenReturn(CREATE_RESPONSE); + zephyrFacade.createTestCase(test); + + assertThat(logger.getLoggingEvents(), is(List.of( + info(CREATE_LINK_FROM_TO, TESTS, ISSUE_ID, REQUIREMENT_ID), + info("Creating Test Case: {}", BODY), + info("Test with key {} has been created", ISSUE_ID)))); + } + private void mockJiraProjectRetrieve() throws IOException, JiraConfigurationException { Version version = new Version(); @@ -266,4 +317,24 @@ private void setConfiguration() statuses.put(TestCaseStatus.PASSED, TEST); when(zephyrExporterConfiguration.getStatuses()).thenReturn(statuses); } + + private ZephyrTestCase createZephyrTestCase() + { + ZephyrTestCase test = new ZephyrTestCase(); + test.setLabels(new LinkedHashSet<>(List.of("label"))); + test.setComponents(new LinkedHashSet<>(List.of("component"))); + test.setTestSteps(List.of(new CucumberTestStep("testStep 1"), new CucumberTestStep("testStep 2"))); + return test; + } + + private void mockSerialization(ZephyrTestCase test) throws IOException + { + doAnswer(a -> + { + JsonGenerator generator = a.getArgument(1, JsonGenerator.class); + generator.writeStartObject(); + generator.writeEndObject(); + return null; + }).when(testStepsSerializer).serialize(eq(test), any(JsonGenerator.class), any(SerializerProvider.class)); + } } diff --git a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/parser/TestCaseParserTests.java b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/parser/TestCaseParserTests.java index 5c3e06875c..11b0241bfa 100644 --- a/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/parser/TestCaseParserTests.java +++ b/vividus-to-zephyr-exporter/src/test/java/org/vividus/zephyr/parser/TestCaseParserTests.java @@ -52,7 +52,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.vividus.zephyr.configuration.ZephyrExporterProperties; import org.vividus.zephyr.databind.TestCaseDeserializer; -import org.vividus.zephyr.model.TestCase; +import org.vividus.zephyr.model.TestCaseExecution; import org.vividus.zephyr.model.TestCaseStatus; import uk.org.lidalia.slf4jext.Level; @@ -118,8 +118,8 @@ void testCreateTestCases() throws URISyntaxException, IOException when(zephyrExporterProperties.getSourceDirectory()).thenReturn(sourceDirectory); when(zephyrExporterProperties.getStatusesOfTestCasesToAddToExecution()) .thenReturn(List.of(TestCaseStatus.SKIPPED, TestCaseStatus.PASSED)); - List testCases = testCaseParser.createTestCases(objectMapper); - assertEquals(testCases.size(), 2); + List testCaseExecutions = testCaseParser.createTestCases(objectMapper); + assertEquals(testCaseExecutions.size(), 2); List events = testLogger.getLoggingEvents(); assertThat(events.get(0).getMessage(), is(JSON_FILES_STRING)); assertThat(events.get(0).getLevel(), is(Level.INFO)); @@ -138,14 +138,14 @@ void testCreateTestCasesWithStatusFilter() throws URISyntaxException, IOExceptio when(zephyrExporterProperties.getSourceDirectory()).thenReturn(sourceDirectory); when(zephyrExporterProperties.getStatusesOfTestCasesToAddToExecution()) .thenReturn(List.of(TestCaseStatus.PASSED)); - List testCases = testCaseParser.createTestCases(objectMapper); - assertEquals(testCases.size(), 1); + List testCaseExecutions = testCaseParser.createTestCases(objectMapper); + assertEquals(testCaseExecutions.size(), 1); List events = testLogger.getLoggingEvents(); assertThat(events.get(0).getMessage(), is(JSON_FILES_STRING)); assertThat(events.get(0).getLevel(), is(Level.INFO)); assertThat(events.get(1).getMessage(), is(TEST_CASES_STRING)); assertThat(events.get(1).getLevel(), is(Level.INFO)); - assertThat(events.get(2), is(info(FOR_EXPORTING_STRING, testCases))); + assertThat(events.get(2), is(info(FOR_EXPORTING_STRING, testCaseExecutions))); assertThat(events.size(), equalTo(3)); } @@ -155,6 +155,7 @@ private ObjectMapper configureObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) .build() - .registerModule(new SimpleModule().addDeserializer(TestCase.class, new TestCaseDeserializer())); + .registerModule(new SimpleModule() + .addDeserializer(TestCaseExecution.class, new TestCaseDeserializer())); } } diff --git a/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/databind/report.json b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/databind/report.json new file mode 100644 index 0000000000..1983ec4e85 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/databind/report.json @@ -0,0 +1,28 @@ +{ + "fields": { + "project": { + "key": "project key" + }, + "issuetype": { + "name": "Test" + }, + "labels": [ + "label 1", + "label 2" + ], + "components": [ + { + "name": "component 1" + }, + { + "name": "component 2" + } + ], + "story-type": { + "value": "Task" + }, + "status": { + "name": "Backlog" + } + } +} diff --git a/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/createreports/test-report.json b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/createreports/test-report.json new file mode 100644 index 0000000000..ff48446167 --- /dev/null +++ b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/createreports/test-report.json @@ -0,0 +1,36 @@ +{ + "path": "storyPath", + "title": "", + "lifecycle": { + "keyword": "Lifecycle:" + }, + "beforeStorySteps": [], + "scenarios": [ + { + "keyword": "Scenario:", + "title": "scenarioTitle", + "meta": [ + { + "keyword": "@", + "name": "requirementId", + "value": "STUB-REQ-0" + } + ], + "steps": [ + { + "outcome": "comment", + "value": "!-- Step: Step" + }, + { + "outcome": "comment", + "value": "!-- Data: Data" + }, + { + "outcome": "comment", + "value": "!-- Result: Result" + } + ] + } + ], + "afterStorySteps": [] +} diff --git a/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/updatereports/test-report-with-testcaseid.json b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/updatereports/test-report-with-testcaseid.json new file mode 100644 index 0000000000..ba1ff8bcef --- /dev/null +++ b/vividus-to-zephyr-exporter/src/test/resources/org/vividus/zephyr/exporter/updatereports/test-report-with-testcaseid.json @@ -0,0 +1,67 @@ +{ + "path": "storyPath", + "title": "", + "lifecycle": { + "keyword": "Lifecycle:" + }, + "beforeStorySteps": [], + "scenarios": [ + { + "keyword": "Scenario:", + "title": "scenarioTitle", + "meta": [ + { + "keyword": "@", + "name": "testCaseId", + "value": "STUB-0" + }, + { + "keyword": "@", + "name": "requirementId", + "value": "" + }, + { + "keyword": "@", + "name": "zephyr.components", + "value": "dummy-component-1; dummy-component-2" + }, + { + "keyword": "@", + "name": "zephyr.labels", + "value": "dummy-label-1; dummy-label-2" + } + ], + "examples": { + "keyword": "Examples:", + "steps": [ + "!-- Comment" + ], + "parameters": { + "names": [], + "values": [] + }, + "examples": [ + { + "keyword": "Example:", + "value": "{zephyr.labels=dummy-label-1; dummy-label-2, zephyr.components=dummy-label-1; dummy-label-2, key=stub, testCaseId=STUB-1}", + "steps": [ + { + "outcome": "comment", + "value": "!-- Step: Step" + }, + { + "outcome": "comment", + "value": "!-- Data: Data" + }, + { + "outcome": "comment", + "value": "!-- Result: Result" + } + ] + } + ] + } + } + ], + "afterStorySteps": [] +}