From 5595bb6e5cfff8ba2e10d97da9d9e680b4016bb6 Mon Sep 17 00:00:00 2001 From: QSD_m Date: Fri, 1 Nov 2019 12:53:32 +0100 Subject: [PATCH 01/98] Initial commit --- .gitignore | 9 + build.gradle | 43 + gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55190 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++ gradlew.bat | 84 ++ settings.gradle | 1 + .../java/com/pubnub/api/vendor/Base64.java | 746 ++++++++++++++++++ src/main/kotlin/com/pubnub/api/Endpoint.kt | 275 +++++++ .../kotlin/com/pubnub/api/PNConfiguration.kt | 82 ++ src/main/kotlin/com/pubnub/api/PubNub.kt | 49 ++ src/main/kotlin/com/pubnub/api/PubNubError.kt | 70 ++ .../kotlin/com/pubnub/api/PubNubException.kt | 19 + src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 28 + .../com/pubnub/api/endpoints/History.kt | 145 ++++ .../com/pubnub/api/endpoints/Publish.kt | 115 +++ .../kotlin/com/pubnub/api/endpoints/Time.kt | 34 + .../enums/PNHeartbeatNotificationOptions.kt | 7 + .../com/pubnub/api/enums/PNLogVerbosity.kt | 6 + .../com/pubnub/api/enums/PNOperationType.kt | 75 ++ .../pubnub/api/enums/PNReconnectionPolicy.kt | 7 + .../com/pubnub/api/enums/PNStatusCategory.kt | 24 + .../pubnub/api/managers/BasePathManager.kt | 15 + .../com/pubnub/api/managers/MapperManager.kt | 114 +++ .../api/managers/PublishSequenceManager.kt | 17 + .../pubnub/api/managers/RetrofitManager.kt | 64 ++ .../pubnub/api/models/consumer/PNErrorData.kt | 12 + .../api/models/consumer/PNPublishResult.kt | 5 + .../pubnub/api/models/consumer/PNStatus.kt | 34 + .../api/models/consumer/PNTimeResult.kt | 5 + .../consumer/history/PNHistoryItemResult.kt | 17 + .../consumer/history/PNHistoryResult.kt | 7 + .../com/pubnub/api/services/HistoryService.kt | 17 + .../com/pubnub/api/services/PublishService.kt | 26 + .../com/pubnub/api/services/TimeService.kt | 11 + .../kotlin/com/pubnub/api/vendor/Crypto.kt | 135 ++++ src/test/kotlin/com/pubnub/tests/AppTest.kt | 69 ++ 38 files changed, 2546 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/pubnub/api/vendor/Base64.java create mode 100644 src/main/kotlin/com/pubnub/api/Endpoint.kt create mode 100644 src/main/kotlin/com/pubnub/api/PNConfiguration.kt create mode 100644 src/main/kotlin/com/pubnub/api/PubNub.kt create mode 100644 src/main/kotlin/com/pubnub/api/PubNubError.kt create mode 100644 src/main/kotlin/com/pubnub/api/PubNubException.kt create mode 100644 src/main/kotlin/com/pubnub/api/PubNubUtil.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/History.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/Publish.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/Time.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNHeartbeatNotificationOptions.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNLogVerbosity.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNReconnectionPolicy.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNStatusCategory.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/MapperManager.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/HistoryService.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/PublishService.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/TimeService.kt create mode 100644 src/main/kotlin/com/pubnub/api/vendor/Crypto.kt create mode 100644 src/test/kotlin/com/pubnub/tests/AppTest.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c566e2044 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.gradle/ +.idea/ +build/ +*.iml +*.ipr +*.iws +*.class +bin/ +gen/ \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..398f1e605 --- /dev/null +++ b/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.50' + id 'java-library' + id 'maven' + id 'io.gitlab.arturbosch.detekt' version '1.1.1' +} + +group 'com.pubnub' +version '0.0.1' + +repositories { + mavenCentral() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testImplementation "org.junit.jupiter:junit-jupiter:5.5.2" + + implementation("com.squareup.retrofit2:retrofit:2.6.2") + + api group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.12.6' + + api 'com.google.code.gson:gson:2.8.6' + implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.6.2' + + // implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28' + // implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.28' + // implementation group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.28' + // implementation group: 'log4j', name: 'log4j', version: '1.2.17' + + testImplementation 'org.awaitility:awaitility-kotlin:4.0.1' +} + +test { + useJUnitPlatform() +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..29e08e8ca --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..87b738cbd051603d91cc39de6cb000dd98fe6b02 GIT binary patch literal 55190 zcmafaW0WS*vSoFbZQHhO+s0S6%`V%vZQJa!ZQHKus_B{g-pt%P_q|ywBQt-*Stldc z$+IJ3?^KWm27v+sf`9-50uuadKtMnL*BJ;1^6ynvR7H?hQcjE>7)art9Bu0Pcm@7C z@c%WG|JzYkP)<@zR9S^iR_sA`azaL$mTnGKnwDyMa;8yL_0^>Ba^)phg0L5rOPTbm7g*YIRLg-2^{qe^`rb!2KqS zk~5wEJtTdD?)3+}=eby3x6%i)sb+m??NHC^u=tcG8p$TzB<;FL(WrZGV&cDQb?O0GMe6PBV=V z?tTO*5_HTW$xea!nkc~Cnx#cL_rrUGWPRa6l+A{aiMY=<0@8y5OC#UcGeE#I>nWh}`#M#kIn-$A;q@u-p71b#hcSItS!IPw?>8 zvzb|?@Ahb22L(O4#2Sre&l9H(@TGT>#Py)D&eW-LNb!=S;I`ZQ{w;MaHW z#to!~TVLgho_Pm%zq@o{K3Xq?I|MVuVSl^QHnT~sHlrVxgsqD-+YD?Nz9@HA<;x2AQjxP)r6Femg+LJ-*)k%EZ}TTRw->5xOY z9#zKJqjZgC47@AFdk1$W+KhTQJKn7e>A&?@-YOy!v_(}GyV@9G#I?bsuto4JEp;5|N{orxi_?vTI4UF0HYcA( zKyGZ4<7Fk?&LZMQb6k10N%E*$gr#T&HsY4SPQ?yerqRz5c?5P$@6dlD6UQwZJ*Je9 z7n-@7!(OVdU-mg@5$D+R%gt82Lt%&n6Yr4=|q>XT%&^z_D*f*ug8N6w$`woqeS-+#RAOfSY&Rz z?1qYa5xi(7eTCrzCFJfCxc%j{J}6#)3^*VRKF;w+`|1n;Xaojr2DI{!<3CaP`#tXs z*`pBQ5k@JLKuCmovFDqh_`Q;+^@t_;SDm29 zCNSdWXbV?9;D4VcoV`FZ9Ggrr$i<&#Dx3W=8>bSQIU_%vf)#(M2Kd3=rN@^d=QAtC zI-iQ;;GMk|&A++W5#hK28W(YqN%?!yuW8(|Cf`@FOW5QbX|`97fxmV;uXvPCqxBD zJ9iI37iV)5TW1R+fV16y;6}2tt~|0J3U4E=wQh@sx{c_eu)t=4Yoz|%Vp<#)Qlh1V z0@C2ZtlT>5gdB6W)_bhXtcZS)`9A!uIOa`K04$5>3&8An+i9BD&GvZZ=7#^r=BN=k za+=Go;qr(M)B~KYAz|<^O3LJON}$Q6Yuqn8qu~+UkUKK~&iM%pB!BO49L+?AL7N7o z(OpM(C-EY753=G=WwJHE`h*lNLMNP^c^bBk@5MyP5{v7x>GNWH>QSgTe5 z!*GPkQ(lcbEs~)4ovCu!Zt&$${9$u(<4@9%@{U<-ksAqB?6F`bQ;o-mvjr)Jn7F&j$@`il1Mf+-HdBs<-`1FahTxmPMMI)@OtI&^mtijW6zGZ67O$UOv1Jj z;a3gmw~t|LjPkW3!EZ=)lLUhFzvO;Yvj9g`8hm%6u`;cuek_b-c$wS_0M4-N<@3l|88 z@V{Sd|M;4+H6guqMm4|v=C6B7mlpP(+It%0E;W`dxMOf9!jYwWj3*MRk`KpS_jx4c z=hrKBkFK;gq@;wUV2eqE3R$M+iUc+UD0iEl#-rECK+XmH9hLKrC={j@uF=f3UiceB zU5l$FF7#RKjx+6!JHMG5-!@zI-eG=a-!Bs^AFKqN_M26%cIIcSs61R$yuq@5a3c3& z4%zLs!g}+C5%`ja?F`?5-og0lv-;(^e<`r~p$x%&*89_Aye1N)9LNVk?9BwY$Y$$F^!JQAjBJvywXAesj7lTZ)rXuxv(FFNZVknJha99lN=^h`J2> zl5=~(tKwvHHvh|9-41@OV`c;Ws--PE%{7d2sLNbDp;A6_Ka6epzOSFdqb zBa0m3j~bT*q1lslHsHqaHIP%DF&-XMpCRL(v;MV#*>mB^&)a=HfLI7efblG z(@hzN`|n+oH9;qBklb=d^S0joHCsArnR1-h{*dIUThik>ot^!6YCNjg;J_i3h6Rl0ji)* zo(tQ~>xB!rUJ(nZjCA^%X;)H{@>uhR5|xBDA=d21p@iJ!cH?+%U|VSh2S4@gv`^)^ zNKD6YlVo$%b4W^}Rw>P1YJ|fTb$_(7C;hH+ z1XAMPb6*p^h8)e5nNPKfeAO}Ik+ZN_`NrADeeJOq4Ak;sD~ zTe77no{Ztdox56Xi4UE6S7wRVxJzWxKj;B%v7|FZ3cV9MdfFp7lWCi+W{}UqekdpH zdO#eoOuB3Fu!DU`ErfeoZWJbWtRXUeBzi zBTF-AI7yMC^ntG+8%mn(I6Dw}3xK8v#Ly{3w3_E?J4(Q5JBq~I>u3!CNp~Ekk&YH` z#383VO4O42NNtcGkr*K<+wYZ>@|sP?`AQcs5oqX@-EIqgK@Pmp5~p6O6qy4ml~N{D z{=jQ7k(9!CM3N3Vt|u@%ssTw~r~Z(}QvlROAkQQ?r8OQ3F0D$aGLh zny+uGnH5muJ<67Z=8uilKvGuANrg@s3Vu_lU2ajb?rIhuOd^E@l!Kl0hYIxOP1B~Q zggUmXbh$bKL~YQ#!4fos9UUVG#}HN$lIkM<1OkU@r>$7DYYe37cXYwfK@vrHwm;pg zbh(hEU|8{*d$q7LUm+x&`S@VbW*&p-sWrplWnRM|I{P;I;%U`WmYUCeJhYc|>5?&& zj}@n}w~Oo=l}iwvi7K6)osqa;M8>fRe}>^;bLBrgA;r^ZGgY@IC^ioRmnE&H4)UV5 zO{7egQ7sBAdoqGsso5q4R(4$4Tjm&&C|7Huz&5B0wXoJzZzNc5Bt)=SOI|H}+fbit z-PiF5(NHSy>4HPMrNc@SuEMDuKYMQ--G+qeUPqO_9mOsg%1EHpqoX^yNd~~kbo`cH zlV0iAkBFTn;rVb>EK^V6?T~t~3vm;csx+lUh_%ROFPy0(omy7+_wYjN!VRDtwDu^h4n|xpAMsLepm% zggvs;v8+isCW`>BckRz1MQ=l>K6k^DdT`~sDXTWQ<~+JtY;I~I>8XsAq3yXgxe>`O zZdF*{9@Z|YtS$QrVaB!8&`&^W->_O&-JXn1n&~}o3Z7FL1QE5R*W2W@=u|w~7%EeC1aRfGtJWxImfY-D3t!!nBkWM> zafu>^Lz-ONgT6ExjV4WhN!v~u{lt2-QBN&UxwnvdH|I%LS|J-D;o>@@sA62@&yew0 z)58~JSZP!(lX;da!3`d)D1+;K9!lyNlkF|n(UduR-%g>#{`pvrD^ClddhJyfL7C-(x+J+9&7EsC~^O`&}V%)Ut8^O_7YAXPDpzv8ir4 zl`d)(;imc6r16k_d^)PJZ+QPxxVJS5e^4wX9D=V2zH&wW0-p&OJe=}rX`*->XT=;_qI&)=WHkYnZx6bLoUh_)n-A}SF_ z9z7agNTM5W6}}ui=&Qs@pO5$zHsOWIbd_&%j^Ok5PJ3yUWQw*i4*iKO)_er2CDUME ztt+{Egod~W-fn^aLe)aBz)MOc_?i-stTj}~iFk7u^-gGSbU;Iem06SDP=AEw9SzuF zeZ|hKCG3MV(z_PJg0(JbqTRf4T{NUt%kz&}4S`)0I%}ZrG!jgW2GwP=WTtkWS?DOs znI9LY!dK+1_H0h+i-_~URb^M;4&AMrEO_UlDV8o?E>^3x%ZJyh$JuDMrtYL8|G3If zPf2_Qb_W+V?$#O; zydKFv*%O;Y@o_T_UAYuaqx1isMKZ^32JtgeceA$0Z@Ck0;lHbS%N5)zzAW9iz; z8tTKeK7&qw!8XVz-+pz>z-BeIzr*#r0nB^cntjQ9@Y-N0=e&ZK72vlzX>f3RT@i7@ z=z`m7jNk!9%^xD0ug%ptZnM>F;Qu$rlwo}vRGBIymPL)L|x}nan3uFUw(&N z24gdkcb7!Q56{0<+zu zEtc5WzG2xf%1<@vo$ZsuOK{v9gx^0`gw>@h>ZMLy*h+6ueoie{D#}}` zK2@6Xxq(uZaLFC%M!2}FX}ab%GQ8A0QJ?&!vaI8Gv=vMhd);6kGguDmtuOElru()) zuRk&Z{?Vp!G~F<1#s&6io1`poBqpRHyM^p;7!+L??_DzJ8s9mYFMQ0^%_3ft7g{PD zZd}8E4EV}D!>F?bzcX=2hHR_P`Xy6?FOK)mCj)Ym4s2hh z0OlOdQa@I;^-3bhB6mpw*X5=0kJv8?#XP~9){G-+0ST@1Roz1qi8PhIXp1D$XNqVG zMl>WxwT+K`SdO1RCt4FWTNy3!i?N>*-lbnn#OxFJrswgD7HjuKpWh*o@QvgF&j+CT z{55~ZsUeR1aB}lv#s_7~+9dCix!5(KR#c?K?e2B%P$fvrsZxy@GP#R#jwL{y#Ld$} z7sF>QT6m|}?V;msb?Nlohj7a5W_D$y+4O6eI;Zt$jVGymlzLKscqer9#+p2$0It&u zWY!dCeM6^B^Z;ddEmhi?8`scl=Lhi7W%2|pT6X6^%-=q90DS(hQ-%c+E*ywPvmoF(KqDoW4!*gmQIklm zk#!GLqv|cs(JRF3G?=AYY19{w@~`G3pa z@xR9S-Hquh*&5Yas*VI};(%9%PADn`kzm zeWMJVW=>>wap*9|R7n#!&&J>gq04>DTCMtj{P^d12|2wXTEKvSf?$AvnE!peqV7i4 zE>0G%CSn%WCW1yre?yi9*aFP{GvZ|R4JT}M%x_%Hztz2qw?&28l&qW<6?c6ym{f$d z5YCF+k#yEbjCN|AGi~-NcCG8MCF1!MXBFL{#7q z)HO+WW173?kuI}^Xat;Q^gb4Hi0RGyB}%|~j8>`6X4CPo+|okMbKy9PHkr58V4bX6<&ERU)QlF8%%huUz&f+dwTN|tk+C&&o@Q1RtG`}6&6;ncQuAcfHoxd5AgD7`s zXynq41Y`zRSiOY@*;&1%1z>oNcWTV|)sjLg1X8ijg1Y zbIGL0X*Sd}EXSQ2BXCKbJmlckY(@EWn~Ut2lYeuw1wg?hhj@K?XB@V_ZP`fyL~Yd3n3SyHU-RwMBr6t-QWE5TinN9VD4XVPU; zonIIR!&pGqrLQK)=#kj40Im%V@ij0&Dh0*s!lnTw+D`Dt-xmk-jmpJv$1-E-vfYL4 zqKr#}Gm}~GPE+&$PI@4ag@=M}NYi7Y&HW82Q`@Y=W&PE31D110@yy(1vddLt`P%N^ z>Yz195A%tnt~tvsSR2{m!~7HUc@x<&`lGX1nYeQUE(%sphTi>JsVqSw8xql*Ys@9B z>RIOH*rFi*C`ohwXjyeRBDt8p)-u{O+KWP;$4gg||%*u{$~yEj+Al zE(hAQRQ1k7MkCq9s4^N3ep*$h^L%2Vq?f?{+cicpS8lo)$Cb69b98au+m2J_e7nYwID0@`M9XIo1H~|eZFc8Hl!qly612ADCVpU zY8^*RTMX(CgehD{9v|^9vZ6Rab`VeZ2m*gOR)Mw~73QEBiktViBhR!_&3l$|be|d6 zupC`{g89Y|V3uxl2!6CM(RNpdtynaiJ~*DqSTq9Mh`ohZnb%^3G{k;6%n18$4nAqR zjPOrP#-^Y9;iw{J@XH9=g5J+yEVh|e=4UeY<^65`%gWtdQ=-aqSgtywM(1nKXh`R4 zzPP&7r)kv_uC7X9n=h=!Zrf<>X=B5f<9~Q>h#jYRD#CT7D~@6@RGNyO-#0iq0uHV1 zPJr2O4d_xLmg2^TmG7|dpfJ?GGa`0|YE+`2Rata9!?$j#e9KfGYuLL(*^z z!SxFA`$qm)q-YKh)WRJZ@S+-sD_1E$V?;(?^+F3tVcK6 z2fE=8hV*2mgiAbefU^uvcM?&+Y&E}vG=Iz!%jBF7iv){lyC`)*yyS~D8k+Mx|N3bm zI~L~Z$=W9&`x)JnO;8c>3LSDw!fzN#X3qi|0`sXY4?cz{*#xz!kvZ9bO=K3XbN z5KrgN=&(JbXH{Wsu9EdmQ-W`i!JWEmfI;yVTT^a-8Ch#D8xf2dtyi?7p z%#)W3n*a#ndFpd{qN|+9Jz++AJQO#-Y7Z6%*%oyEP5zs}d&kKIr`FVEY z;S}@d?UU=tCdw~EJ{b}=9x}S2iv!!8<$?d7VKDA8h{oeD#S-$DV)-vPdGY@x08n)@ zag?yLF_E#evvRTj4^CcrLvBL=fft&@HOhZ6Ng4`8ijt&h2y}fOTC~7GfJi4vpomA5 zOcOM)o_I9BKz}I`q)fu+Qnfy*W`|mY%LO>eF^a z;$)?T4F-(X#Q-m}!-k8L_rNPf`Mr<9IWu)f&dvt=EL+ESYmCvErd@8B9hd)afc(ZL94S z?rp#h&{7Ah5IJftK4VjATklo7@hm?8BX*~oBiz)jyc9FuRw!-V;Uo>p!CWpLaIQyt zAs5WN)1CCeux-qiGdmbIk8LR`gM+Qg=&Ve}w?zA6+sTL)abU=-cvU`3E?p5$Hpkxw znu0N659qR=IKnde*AEz_7z2pdi_Bh-sb3b=PdGO1Pdf_q2;+*Cx9YN7p_>rl``knY zRn%aVkcv1(W;`Mtp_DNOIECtgq%ufk-mu_<+Fu3Q17Tq4Rr(oeq)Yqk_CHA7LR@7@ zIZIDxxhS&=F2IQfusQ+Nsr%*zFK7S4g!U0y@3H^Yln|i;0a5+?RPG;ZSp6Tul>ezM z`40+516&719qT)mW|ArDSENle5hE2e8qY+zfeZoy12u&xoMgcP)4=&P-1Ib*-bAy` zlT?>w&B|ei-rCXO;sxo7*G;!)_p#%PAM-?m$JP(R%x1Hfas@KeaG%LO?R=lmkXc_MKZW}3f%KZ*rAN?HYvbu2L$ zRt_uv7~-IejlD1x;_AhwGXjB94Q=%+PbxuYzta*jw?S&%|qb=(JfJ?&6P=R7X zV%HP_!@-zO*zS}46g=J}#AMJ}rtWBr21e6hOn&tEmaM%hALH7nlm2@LP4rZ>2 zebe5aH@k!e?ij4Zwak#30|}>;`bquDQK*xmR=zc6vj0yuyC6+U=LusGnO3ZKFRpen z#pwzh!<+WBVp-!$MAc<0i~I%fW=8IO6K}bJ<-Scq>e+)951R~HKB?Mx2H}pxPHE@} zvqpq5j81_jtb_WneAvp<5kgdPKm|u2BdQx9%EzcCN&U{l+kbkhmV<1}yCTDv%&K^> zg;KCjwh*R1f_`6`si$h6`jyIKT7rTv5#k~x$mUyIw)_>Vr)D4fwIs@}{FSX|5GB1l z4vv;@oS@>Bu7~{KgUa_8eg#Lk6IDT2IY$41$*06{>>V;Bwa(-@N;ex4;D`(QK*b}{ z{#4$Hmt)FLqERgKz=3zXiV<{YX6V)lvYBr3V>N6ajeI~~hGR5Oe>W9r@sg)Na(a4- zxm%|1OKPN6^%JaD^^O~HbLSu=f`1px>RawOxLr+1b2^28U*2#h*W^=lSpSY4(@*^l z{!@9RSLG8Me&RJYLi|?$c!B0fP=4xAM4rerxX{xy{&i6=AqXueQAIBqO+pmuxy8Ib z4X^}r!NN3-upC6B#lt7&x0J;)nb9O~xjJMemm$_fHuP{DgtlU3xiW0UesTzS30L+U zQzDI3p&3dpONhd5I8-fGk^}@unluzu%nJ$9pzoO~Kk!>dLxw@M)M9?pNH1CQhvA`z zV;uacUtnBTdvT`M$1cm9`JrT3BMW!MNVBy%?@ZX%;(%(vqQAz<7I!hlDe|J3cn9=} zF7B;V4xE{Ss76s$W~%*$JviK?w8^vqCp#_G^jN0j>~Xq#Zru26e#l3H^{GCLEXI#n z?n~F-Lv#hU(bZS`EI9(xGV*jT=8R?CaK)t8oHc9XJ;UPY0Hz$XWt#QyLBaaz5+}xM zXk(!L_*PTt7gwWH*HLWC$h3Ho!SQ-(I||nn_iEC{WT3S{3V{8IN6tZ1C+DiFM{xlI zeMMk{o5;I6UvaC)@WKp9D+o?2Vd@4)Ue-nYci()hCCsKR`VD;hr9=vA!cgGL%3k^b(jADGyPi2TKr(JNh8mzlIR>n(F_hgiV(3@Ds(tjbNM7GoZ;T|3 zWzs8S`5PrA!9){jBJuX4y`f<4;>9*&NY=2Sq2Bp`M2(fox7ZhIDe!BaQUb@P(ub9D zlP8!p(AN&CwW!V&>H?yPFMJ)d5x#HKfwx;nS{Rr@oHqpktOg)%F+%1#tsPtq7zI$r zBo-Kflhq-=7_eW9B2OQv=@?|y0CKN77)N;z@tcg;heyW{wlpJ1t`Ap!O0`Xz{YHqO zI1${8Hag^r!kA<2_~bYtM=<1YzQ#GGP+q?3T7zYbIjN6Ee^V^b&9en$8FI*NIFg9G zPG$OXjT0Ku?%L7fat8Mqbl1`azf1ltmKTa(HH$Dqlav|rU{zP;Tbnk-XkGFQ6d+gi z-PXh?_kEJl+K98&OrmzgPIijB4!Pozbxd0H1;Usy!;V>Yn6&pu*zW8aYx`SC!$*ti zSn+G9p=~w6V(fZZHc>m|PPfjK6IN4(o=IFu?pC?+`UZAUTw!e`052{P=8vqT^(VeG z=psASIhCv28Y(;7;TuYAe>}BPk5Qg=8$?wZj9lj>h2kwEfF_CpK=+O6Rq9pLn4W)# zeXCKCpi~jsfqw7Taa0;!B5_C;B}e56W1s8@p*)SPzA;Fd$Slsn^=!_&!mRHV*Lmt| zBGIDPuR>CgS4%cQ4wKdEyO&Z>2aHmja;Pz+n|7(#l%^2ZLCix%>@_mbnyPEbyrHaz z>j^4SIv;ZXF-Ftzz>*t4wyq)ng8%0d;(Z_ExZ-cxwei=8{(br-`JYO(f23Wae_MqE z3@{Mlf^%M5G1SIN&en1*| zH~ANY1h3&WNsBy$G9{T=`kcxI#-X|>zLX2r*^-FUF+m0{k)n#GTG_mhG&fJfLj~K& zU~~6othMlvMm9<*SUD2?RD+R17|Z4mgR$L*R3;nBbo&Vm@39&3xIg;^aSxHS>}gwR zmzs?h8oPnNVgET&dx5^7APYx6Vv6eou07Zveyd+^V6_LzI$>ic+pxD_8s~ zC<}ucul>UH<@$KM zT4oI=62M%7qQO{}re-jTFqo9Z;rJKD5!X5$iwUsh*+kcHVhID08MB5cQD4TBWB(rI zuWc%CA}}v|iH=9gQ?D$1#Gu!y3o~p7416n54&Hif`U-cV?VrUMJyEqo_NC4#{puzU zzXEE@UppeeRlS9W*^N$zS`SBBi<@tT+<%3l@KhOy^%MWB9(A#*J~DQ;+MK*$rxo6f zcx3$3mcx{tly!q(p2DQrxcih|)0do_ZY77pyHGE#Q(0k*t!HUmmMcYFq%l$-o6%lS zDb49W-E?rQ#Hl``C3YTEdGZjFi3R<>t)+NAda(r~f1cT5jY}s7-2^&Kvo&2DLTPYP zhVVo-HLwo*vl83mtQ9)PR#VBg)FN}+*8c-p8j`LnNUU*Olm1O1Qqe62D#$CF#?HrM zy(zkX|1oF}Z=T#3XMLWDrm(|m+{1&BMxHY7X@hM_+cV$5-t!8HT(dJi6m9{ja53Yw z3f^`yb6Q;(e|#JQIz~B*=!-GbQ4nNL-NL z@^NWF_#w-Cox@h62;r^;Y`NX8cs?l^LU;5IWE~yvU8TqIHij!X8ydbLlT0gwmzS9} z@5BccG?vO;rvCs$mse1*ANi-cYE6Iauz$Fbn3#|ToAt5v7IlYnt6RMQEYLldva{~s zvr>1L##zmeoYgvIXJ#>bbuCVuEv2ZvZ8I~PQUN3wjP0UC)!U+wn|&`V*8?)` zMSCuvnuGec>QL+i1nCPGDAm@XSMIo?A9~C?g2&G8aNKjWd2pDX{qZ?04+2 zeyLw}iEd4vkCAWwa$ zbrHlEf3hfN7^1g~aW^XwldSmx1v~1z(s=1az4-wl} z`mM+G95*N*&1EP#u3}*KwNrPIgw8Kpp((rdEOO;bT1;6ea~>>sK+?!;{hpJ3rR<6UJb`O8P4@{XGgV%63_fs%cG8L zk9Fszbdo4tS$g0IWP1>t@0)E%-&9yj%Q!fiL2vcuL;90fPm}M==<>}Q)&sp@STFCY z^p!RzmN+uXGdtPJj1Y-khNyCb6Y$Vs>eZyW zPaOV=HY_T@FwAlleZCFYl@5X<<7%5DoO(7S%Lbl55?{2vIr_;SXBCbPZ(up;pC6Wx={AZL?shYOuFxLx1*>62;2rP}g`UT5+BHg(ju z&7n5QSvSyXbioB9CJTB#x;pexicV|9oaOpiJ9VK6EvKhl4^Vsa(p6cIi$*Zr0UxQ z;$MPOZnNae2Duuce~7|2MCfhNg*hZ9{+8H3?ts9C8#xGaM&sN;2lriYkn9W>&Gry! z3b(Xx1x*FhQkD-~V+s~KBfr4M_#0{`=Yrh90yj}Ph~)Nx;1Y^8<418tu!$1<3?T*~ z7Dl0P3Uok-7w0MPFQexNG1P5;y~E8zEvE49>$(f|XWtkW2Mj`udPn)pb%} zrA%wRFp*xvDgC767w!9`0vx1=q!)w!G+9(-w&p*a@WXg{?T&%;qaVcHo>7ca%KX$B z^7|KBPo<2;kM{2mRnF8vKm`9qGV%|I{y!pKm8B(q^2V;;x2r!1VJ^Zz8bWa)!-7a8 zSRf@dqEPlsj!7}oNvFFAA)75})vTJUwQ03hD$I*j6_5xbtd_JkE2`IJD_fQ;a$EkO z{fQ{~e%PKgPJsD&PyEvDmg+Qf&p*-qu!#;1k2r_(H72{^(Z)htgh@F?VIgK#_&eS- z$~(qInec>)XIkv@+{o6^DJLpAb>!d}l1DK^(l%#OdD9tKK6#|_R?-%0V!`<9Hj z3w3chDwG*SFte@>Iqwq`J4M&{aHXzyigT620+Vf$X?3RFfeTcvx_e+(&Q*z)t>c0e zpZH$1Z3X%{^_vylHVOWT6tno=l&$3 z9^eQ@TwU#%WMQaFvaYp_we%_2-9=o{+ck zF{cKJCOjpW&qKQquyp2BXCAP920dcrZ}T1@piukx_NY;%2W>@Wca%=Ch~x5Oj58Hv z;D-_ALOZBF(Mqbcqjd}P3iDbek#Dwzu`WRs`;hRIr*n0PV7vT+%Io(t}8KZ zpp?uc2eW!v28ipep0XNDPZt7H2HJ6oey|J3z!ng#1H~x_k%35P+Cp%mqXJ~cV0xdd z^4m5^K_dQ^Sg?$P`))ccV=O>C{Ds(C2WxX$LMC5vy=*44pP&)X5DOPYfqE${)hDg< z3hcG%U%HZ39=`#Ko4Uctg&@PQLf>?0^D|4J(_1*TFMOMB!Vv1_mnOq$BzXQdOGqgy zOp#LBZ!c>bPjY1NTXksZmbAl0A^Y&(%a3W-k>bE&>K?px5Cm%AT2E<&)Y?O*?d80d zgI5l~&Mve;iXm88Q+Fw7{+`PtN4G7~mJWR^z7XmYQ>uoiV!{tL)hp|= zS(M)813PM`d<501>{NqaPo6BZ^T{KBaqEVH(2^Vjeq zgeMeMpd*1tE@@);hGjuoVzF>Cj;5dNNwh40CnU+0DSKb~GEMb_# zT8Z&gz%SkHq6!;_6dQFYE`+b`v4NT7&@P>cA1Z1xmXy<2htaDhm@XXMp!g($ zw(7iFoH2}WR`UjqjaqOQ$ecNt@c|K1H1kyBArTTjLp%-M`4nzOhkfE#}dOpcd;b#suq8cPJ&bf5`6Tq>ND(l zib{VrPZ>{KuaIg}Y$W>A+nrvMg+l4)-@2jpAQ5h(Tii%Ni^-UPVg{<1KGU2EIUNGaXcEkOedJOusFT9X3%Pz$R+-+W+LlRaY-a$5r?4V zbPzgQl22IPG+N*iBRDH%l{Zh$fv9$RN1sU@Hp3m=M}{rX%y#;4(x1KR2yCO7Pzo>rw(67E{^{yUR`91nX^&MxY@FwmJJbyPAoWZ9Z zcBS$r)&ogYBn{DOtD~tIVJUiq|1foX^*F~O4hlLp-g;Y2wKLLM=?(r3GDqsPmUo*? zwKMEi*%f)C_@?(&&hk>;m07F$X7&i?DEK|jdRK=CaaNu-)pX>n3}@%byPKVkpLzBq z{+Py&!`MZ^4@-;iY`I4#6G@aWMv{^2VTH7|WF^u?3vsB|jU3LgdX$}=v7#EHRN(im zI(3q-eU$s~r=S#EWqa_2!G?b~ z<&brq1vvUTJH380=gcNntZw%7UT8tLAr-W49;9y^=>TDaTC|cKA<(gah#2M|l~j)w zY8goo28gj$n&zcNgqX1Qn6=<8?R0`FVO)g4&QtJAbW3G#D)uNeac-7cH5W#6i!%BH z=}9}-f+FrtEkkrQ?nkoMQ1o-9_b+&=&C2^h!&mWFga#MCrm85hW;)1pDt;-uvQG^D zntSB?XA*0%TIhtWDS!KcI}kp3LT>!(Nlc(lQN?k^bS8Q^GGMfo}^|%7s;#r+pybl@?KA++|FJ zr%se9(B|g*ERQU96az%@4gYrxRRxaM2*b}jNsG|0dQi;Rw{0WM0E>rko!{QYAJJKY z)|sX0N$!8d9E|kND~v|f>3YE|uiAnqbkMn)hu$if4kUkzKqoNoh8v|S>VY1EKmgO} zR$0UU2o)4i4yc1inx3}brso+sio{)gfbLaEgLahj8(_Z#4R-v) zglqwI%`dsY+589a8$Mu7#7_%kN*ekHupQ#48DIN^uhDxblDg3R1yXMr^NmkR z7J_NWCY~fhg}h!_aXJ#?wsZF$q`JH>JWQ9`jbZzOBpS`}-A$Vgkq7+|=lPx9H7QZG z8i8guMN+yc4*H*ANr$Q-3I{FQ-^;8ezWS2b8rERp9TMOLBxiG9J*g5=?h)mIm3#CGi4JSq1ohFrcrxx@`**K5%T}qbaCGldV!t zVeM)!U3vbf5FOy;(h08JnhSGxm)8Kqxr9PsMeWi=b8b|m_&^@#A3lL;bVKTBx+0v8 zLZeWAxJ~N27lsOT2b|qyp$(CqzqgW@tyy?CgwOe~^i;ZH zlL``i4r!>i#EGBNxV_P@KpYFQLz4Bdq{#zA&sc)*@7Mxsh9u%e6Ke`?5Yz1jkTdND zR8!u_yw_$weBOU}24(&^Bm|(dSJ(v(cBct}87a^X(v>nVLIr%%D8r|&)mi+iBc;B;x;rKq zd8*X`r?SZsTNCPQqoFOrUz8nZO?225Z#z(B!4mEp#ZJBzwd7jW1!`sg*?hPMJ$o`T zR?KrN6OZA1H{9pA;p0cSSu;@6->8aJm1rrO-yDJ7)lxuk#npUk7WNER1Wwnpy%u zF=t6iHzWU(L&=vVSSc^&D_eYP3TM?HN!Tgq$SYC;pSIPWW;zeNm7Pgub#yZ@7WPw#f#Kl)W4%B>)+8%gpfoH1qZ;kZ*RqfXYeGXJ_ zk>2otbp+1By`x^1V!>6k5v8NAK@T;89$`hE0{Pc@Q$KhG0jOoKk--Qx!vS~lAiypV zCIJ&6B@24`!TxhJ4_QS*S5;;Pk#!f(qIR7*(c3dN*POKtQe)QvR{O2@QsM%ujEAWEm) z+PM=G9hSR>gQ`Bv2(k}RAv2+$7qq(mU`fQ+&}*i%-RtSUAha>70?G!>?w%F(b4k!$ zvm;E!)2`I?etmSUFW7WflJ@8Nx`m_vE2HF#)_BiD#FaNT|IY@!uUbd4v$wTglIbIX zblRy5=wp)VQzsn0_;KdM%g<8@>#;E?vypTf=F?3f@SSdZ;XpX~J@l1;p#}_veWHp>@Iq_T z@^7|h;EivPYv1&u0~l9(a~>dV9Uw10QqB6Dzu1G~-l{*7IktljpK<_L8m0|7VV_!S zRiE{u97(%R-<8oYJ{molUd>vlGaE-C|^<`hppdDz<7OS13$#J zZ+)(*rZIDSt^Q$}CRk0?pqT5PN5TT`Ya{q(BUg#&nAsg6apPMhLTno!SRq1e60fl6GvpnwDD4N> z9B=RrufY8+g3_`@PRg+(+gs2(bd;5#{uTZk96CWz#{=&h9+!{_m60xJxC%r&gd_N! z>h5UzVX%_7@CUeAA1XFg_AF%(uS&^1WD*VPS^jcC!M2v@RHZML;e(H-=(4(3O&bX- zI6>usJOS+?W&^S&DL{l|>51ZvCXUKlH2XKJPXnHjs*oMkNM#ZDLx!oaM5(%^)5XaP zk6&+P16sA>vyFe9v`Cp5qnbE#r#ltR5E+O3!WnKn`56Grs2;sqr3r# zp@Zp<^q`5iq8OqOlJ`pIuyK@3zPz&iJ0Jcc`hDQ1bqos2;}O|$i#}e@ua*x5VCSx zJAp}+?Hz++tm9dh3Fvm_bO6mQo38al#>^O0g)Lh^&l82+&x)*<n7^Sw-AJo9tEzZDwyJ7L^i7|BGqHu+ea6(&7jKpBq>~V z8CJxurD)WZ{5D0?s|KMi=e7A^JVNM6sdwg@1Eg_+Bw=9j&=+KO1PG|y(mP1@5~x>d z=@c{EWU_jTSjiJl)d(>`qEJ;@iOBm}alq8;OK;p(1AdH$)I9qHNmxxUArdzBW0t+Qeyl)m3?D09770g z)hzXEOy>2_{?o%2B%k%z4d23!pZcoxyW1Ik{|m7Q1>fm4`wsRrl)~h z_=Z*zYL+EG@DV1{6@5@(Ndu!Q$l_6Qlfoz@79q)Kmsf~J7t1)tl#`MD<;1&CAA zH8;i+oBm89dTTDl{aH`cmTPTt@^K-%*sV+t4X9q0Z{A~vEEa!&rRRr=0Rbz4NFCJr zLg2u=0QK@w9XGE=6(-JgeP}G#WG|R&tfHRA3a9*zh5wNTBAD;@YYGx%#E4{C#Wlfo z%-JuW9=FA_T6mR2-Vugk1uGZvJbFvVVWT@QOWz$;?u6+CbyQsbK$>O1APk|xgnh_8 zc)s@Mw7#0^wP6qTtyNq2G#s?5j~REyoU6^lT7dpX{T-rhZWHD%dik*=EA7bIJgOVf_Ga!yC8V^tkTOEHe+JK@Fh|$kfNxO^= z#lpV^(ZQ-3!^_BhV>aXY~GC9{8%1lOJ}6vzXDvPhC>JrtXwFBC+!3a*Z-%#9}i z#<5&0LLIa{q!rEIFSFc9)>{-_2^qbOg5;_A9 ztQ))C6#hxSA{f9R3Eh^`_f${pBJNe~pIQ`tZVR^wyp}=gLK}e5_vG@w+-mp#Fu>e| z*?qBp5CQ5zu+Fi}xAs)YY1;bKG!htqR~)DB$ILN6GaChoiy%Bq@i+1ZnANC0U&D z_4k$=YP47ng+0NhuEt}6C;9-JDd8i5S>`Ml==9wHDQFOsAlmtrVwurYDw_)Ihfk35 zJDBbe!*LUpg%4n>BExWz>KIQ9vexUu^d!7rc_kg#Bf= z7TLz|l*y*3d2vi@c|pX*@ybf!+Xk|2*z$@F4K#MT8Dt4zM_EcFmNp31#7qT6(@GG? zdd;sSY9HHuDb=w&|K%sm`bYX#%UHKY%R`3aLMO?{T#EI@FNNFNO>p@?W*i0z(g2dt z{=9Ofh80Oxv&)i35AQN>TPMjR^UID-T7H5A?GI{MD_VeXZ%;uo41dVm=uT&ne2h0i zv*xI%9vPtdEK@~1&V%p1sFc2AA`9?H)gPnRdlO~URx!fiSV)j?Tf5=5F>hnO=$d$x zzaIfr*wiIc!U1K*$JO@)gP4%xp!<*DvJSv7p}(uTLUb=MSb@7_yO+IsCj^`PsxEl& zIxsi}s3L?t+p+3FXYqujGhGwTx^WXgJ1}a@Yq5mwP0PvGEr*qu7@R$9j>@-q1rz5T zriz;B^(ex?=3Th6h;7U`8u2sDlfS{0YyydK=*>-(NOm9>S_{U|eg(J~C7O zIe{|LK=Y`hXiF_%jOM8Haw3UtaE{hWdzo3BbD6ud7br4cODBtN(~Hl+odP0SSWPw;I&^m)yLw+nd#}3#z}?UIcX3=SssI}`QwY=% zAEXTODk|MqTx}2DVG<|~(CxgLyi*A{m>M@1h^wiC)4Hy>1K7@|Z&_VPJsaQoS8=ex zDL&+AZdQa>ylxhT_Q$q=60D5&%pi6+qlY3$3c(~rsITX?>b;({FhU!7HOOhSP7>bmTkC8KM%!LRGI^~y3Ug+gh!QM=+NZXznM)?L3G=4=IMvFgX3BAlyJ z`~jjA;2z+65D$j5xbv9=IWQ^&-K3Yh`vC(1Qz2h2`o$>Cej@XRGff!it$n{@WEJ^N z41qk%Wm=}mA*iwCqU_6}Id!SQd13aFER3unXaJJXIsSnxvG2(hSCP{i&QH$tL&TPx zDYJsuk+%laN&OvKb-FHK$R4dy%M7hSB*yj#-nJy?S9tVoxAuDei{s}@+pNT!vLOIC z8g`-QQW8FKp3cPsX%{)0B+x+OhZ1=L7F-jizt|{+f1Ga7%+!BXqjCjH&x|3%?UbN# zh?$I1^YokvG$qFz5ySK+Ja5=mkR&p{F}ev**rWdKMko+Gj^?Or=UH?SCg#0F(&a_y zXOh}dPv0D9l0RVedq1~jCNV=8?vZfU-Xi|nkeE->;ohG3U7z+^0+HV17~-_Mv#mV` zzvwUJJ15v5wwKPv-)i@dsEo@#WEO9zie7mdRAbgL2kjbW4&lk$vxkbq=w5mGKZK6@ zjXWctDkCRx58NJD_Q7e}HX`SiV)TZMJ}~zY6P1(LWo`;yDynY_5_L?N-P`>ALfmyl z8C$a~FDkcwtzK9m$tof>(`Vu3#6r#+v8RGy#1D2)F;vnsiL&P-c^PO)^B-4VeJteLlT@25sPa z%W~q5>YMjj!mhN})p$47VA^v$Jo6_s{!y?}`+h+VM_SN`!11`|;C;B};B&Z<@%FOG z_YQVN+zFF|q5zKab&e4GH|B;sBbKimHt;K@tCH+S{7Ry~88`si7}S)1E{21nldiu5 z_4>;XTJa~Yd$m4A9{Qbd)KUAm7XNbZ4xHbg3a8-+1uf*$1PegabbmCzgC~1WB2F(W zYj5XhVos!X!QHuZXCatkRsdEsSCc+D2?*S7a+(v%toqyxhjz|`zdrUvsxQS{J>?c& zvx*rHw^8b|v^7wq8KWVofj&VUitbm*a&RU_ln#ZFA^3AKEf<#T%8I!Lg3XEsdH(A5 zlgh&M_XEoal)i#0tcq8c%Gs6`xu;vvP2u)D9p!&XNt z!TdF_H~;`g@fNXkO-*t<9~;iEv?)Nee%hVe!aW`N%$cFJ(Dy9+Xk*odyFj72T!(b%Vo5zvCGZ%3tkt$@Wcx8BWEkefI1-~C_3y*LjlQ5%WEz9WD8i^ z2MV$BHD$gdPJV4IaV)G9CIFwiV=ca0cfXdTdK7oRf@lgyPx;_7*RRFk=?@EOb9Gcz zg~VZrzo*Snp&EE{$CWr)JZW)Gr;{B2ka6B!&?aknM-FENcl%45#y?oq9QY z3^1Y5yn&^D67Da4lI}ljDcphaEZw2;tlYuzq?uB4b9Mt6!KTW&ptxd^vF;NbX=00T z@nE1lIBGgjqs?ES#P{ZfRb6f!At51vk%<0X%d_~NL5b8UyfQMPDtfU@>ijA0NP3UU zh{lCf`Wu7cX!go`kUG`1K=7NN@SRGjUKuo<^;@GS!%iDXbJs`o6e`v3O8-+7vRkFm z)nEa$sD#-v)*Jb>&Me+YIW3PsR1)h=-Su)))>-`aRcFJG-8icomO4J@60 zw10l}BYxi{eL+Uu0xJYk-Vc~BcR49Qyyq!7)PR27D`cqGrik=?k1Of>gY7q@&d&Ds zt7&WixP`9~jjHO`Cog~RA4Q%uMg+$z^Gt&vn+d3&>Ux{_c zm|bc;k|GKbhZLr-%p_f%dq$eiZ;n^NxoS-Nu*^Nx5vm46)*)=-Bf<;X#?`YC4tLK; z?;u?shFbXeks+dJ?^o$l#tg*1NA?(1iFff@I&j^<74S!o;SWR^Xi);DM%8XiWpLi0 zQE2dL9^a36|L5qC5+&Pf0%>l&qQ&)OU4vjd)%I6{|H+pw<0(a``9w(gKD&+o$8hOC zNAiShtc}e~ob2`gyVZx59y<6Fpl*$J41VJ-H*e-yECWaDMmPQi-N8XI3 z%iI@ljc+d}_okL1CGWffeaejlxWFVDWu%e=>H)XeZ|4{HlbgC-Uvof4ISYQzZ0Um> z#Ov{k1c*VoN^f(gfiueuag)`TbjL$XVq$)aCUBL_M`5>0>6Ska^*Knk__pw{0I>jA zzh}Kzg{@PNi)fcAk7jMAdi-_RO%x#LQszDMS@_>iFoB+zJ0Q#CQJzFGa8;pHFdi`^ zxnTC`G$7Rctm3G8t8!SY`GwFi4gF|+dAk7rh^rA{NXzc%39+xSYM~($L(pJ(8Zjs* zYdN_R^%~LiGHm9|ElV4kVZGA*T$o@YY4qpJOxGHlUi*S*A(MrgQ{&xoZQo+#PuYRs zv3a$*qoe9gBqbN|y|eaH=w^LE{>kpL!;$wRahY(hhzRY;d33W)m*dfem@)>pR54Qy z ze;^F?mwdU?K+=fBabokSls^6_6At#1Sh7W*y?r6Ss*dmZP{n;VB^LDxM1QWh;@H0J z!4S*_5j_;+@-NpO1KfQd&;C7T`9ak;X8DTRz$hDNcjG}xAfg%gwZSb^zhE~O);NMO zn2$fl7Evn%=Lk!*xsM#(y$mjukN?A&mzEw3W5>_o+6oh62kq=4-`e3B^$rG=XG}Kd zK$blh(%!9;@d@3& zGFO60j1Vf54S}+XD?%*uk7wW$f`4U3F*p7@I4Jg7f`Il}2H<{j5h?$DDe%wG7jZQL zI{mj?t?Hu>$|2UrPr5&QyK2l3mas?zzOk0DV30HgOQ|~xLXDQ8M3o#;CNKO8RK+M; zsOi%)js-MU>9H4%Q)#K_me}8OQC1u;f4!LO%|5toa1|u5Q@#mYy8nE9IXmR}b#sZK z3sD395q}*TDJJA9Er7N`y=w*S&tA;mv-)Sx4(k$fJBxXva0_;$G6!9bGBw13c_Uws zXks4u(8JA@0O9g5f?#V~qR5*u5aIe2HQO^)RW9TTcJk28l`Syl>Q#ZveEE4Em+{?%iz6=V3b>rCm9F zPQQm@-(hfNdo2%n?B)u_&Qh7^^@U>0qMBngH8}H|v+Ejg*Dd(Y#|jgJ-A zQ_bQscil%eY}8oN7ZL+2r|qv+iJY?*l)&3W_55T3GU;?@Om*(M`u0DXAsQ7HSl56> z4P!*(%&wRCb?a4HH&n;lAmr4rS=kMZb74Akha2U~Ktni>>cD$6jpugjULq)D?ea%b zk;UW0pAI~TH59P+o}*c5Ei5L-9OE;OIBt>^(;xw`>cN2`({Rzg71qrNaE=cAH^$wP zNrK9Glp^3a%m+ilQj0SnGq`okjzmE7<3I{JLD6Jn^+oas=h*4>Wvy=KXqVBa;K&ri z4(SVmMXPG}0-UTwa2-MJ=MTfM3K)b~DzSVq8+v-a0&Dsv>4B65{dBhD;(d44CaHSM zb!0ne(*<^Q%|nuaL`Gb3D4AvyO8wyygm=1;9#u5x*k0$UOwx?QxR*6Od8>+ujfyo0 zJ}>2FgW_iv(dBK2OWC-Y=Tw!UwIeOAOUUC;h95&S1hn$G#if+d;*dWL#j#YWswrz_ zMlV=z+zjZJ%SlDhxf)vv@`%~$Afd)T+MS1>ZE7V$Rj#;J*<9Ld=PrK0?qrazRJWx) z(BTLF@Wk279nh|G%ZY7_lK7=&j;x`bMND=zgh_>>-o@6%8_#Bz!FnF*onB@_k|YCF z?vu!s6#h9bL3@tPn$1;#k5=7#s*L;FLK#=M89K^|$3LICYWIbd^qguQp02w5>8p-H z+@J&+pP_^iF4Xu>`D>DcCnl8BUwwOlq6`XkjHNpi@B?OOd`4{dL?kH%lt78(-L}eah8?36zw9d-dI6D{$s{f=M7)1 zRH1M*-82}DoFF^Mi$r}bTB5r6y9>8hjL54%KfyHxn$LkW=AZ(WkHWR;tIWWr@+;^^ zVomjAWT)$+rn%g`LHB6ZSO@M3KBA? z+W7ThSBgpk`jZHZUrp`F;*%6M5kLWy6AW#T{jFHTiKXP9ITrMlEdti7@&AT_a-BA!jc(Kt zWk>IdY-2Zbz?U1)tk#n_Lsl?W;0q`;z|t9*g-xE!(}#$fScX2VkjSiboKWE~afu5d z2B@9mvT=o2fB_>Mnie=TDJB+l`GMKCy%2+NcFsbpv<9jS@$X37K_-Y!cvF5NEY`#p z3sWEc<7$E*X*fp+MqsOyMXO=<2>o8)E(T?#4KVQgt=qa%5FfUG_LE`n)PihCz2=iNUt7im)s@;mOc9SR&{`4s9Q6)U31mn?}Y?$k3kU z#h??JEgH-HGt`~%)1ZBhT9~uRi8br&;a5Y3K_Bl1G)-y(ytx?ok9S*Tz#5Vb=P~xH z^5*t_R2It95=!XDE6X{MjLYn4Eszj9Y91T2SFz@eYlx9Z9*hWaS$^5r7=W5|>sY8}mS(>e9Ez2qI1~wtlA$yv2e-Hjn&K*P z2zWSrC~_8Wrxxf#%QAL&f8iH2%R)E~IrQLgWFg8>`Vnyo?E=uiALoRP&qT{V2{$79 z%9R?*kW-7b#|}*~P#cA@q=V|+RC9=I;aK7Pju$K-n`EoGV^-8Mk=-?@$?O37evGKn z3NEgpo_4{s>=FB}sqx21d3*=gKq-Zk)U+bM%Q_}0`XGkYh*+jRaP+aDnRv#Zz*n$pGp zEU9omuYVXH{AEx>=kk}h2iKt!yqX=EHN)LF}z1j zJx((`CesN1HxTFZ7yrvA2jTPmKYVij>45{ZH2YtsHuGzIRotIFj?(8T@ZWUv{_%AI zgMZlB03C&FtgJqv9%(acqt9N)`4jy4PtYgnhqev!r$GTIOvLF5aZ{tW5MN@9BDGu* zBJzwW3sEJ~Oy8is`l6Ly3an7RPtRr^1Iu(D!B!0O241Xua>Jee;Rc7tWvj!%#yX#m z&pU*?=rTVD7pF6va1D@u@b#V@bShFr3 zMyMbNCZwT)E-%L-{%$3?n}>EN>ai7b$zR_>=l59mW;tfKj^oG)>_TGCJ#HbLBsNy$ zqAqPagZ3uQ(Gsv_-VrZmG&hHaOD#RB#6J8&sL=^iMFB=gH5AIJ+w@sTf7xa&Cnl}@ zxrtzoNq>t?=(+8bS)s2p3>jW}tye0z2aY_Dh@(18-vdfvn;D?sv<>UgL{Ti08$1Q+ zZI3q}yMA^LK=d?YVg({|v?d1|R?5 zL0S3fw)BZazRNNX|7P4rh7!+3tCG~O8l+m?H} z(CB>8(9LtKYIu3ohJ-9ecgk+L&!FX~Wuim&;v$>M4 zUfvn<=Eok(63Ubc>mZrd8d7(>8bG>J?PtOHih_xRYFu1Hg{t;%+hXu2#x%a%qzcab zv$X!ccoj)exoOnaco_jbGw7KryOtuf(SaR-VJ0nAe(1*AA}#QV1lMhGtzD>RoUZ;WA?~!K{8%chYn?ttlz17UpDLlhTkGcVfHY6R<2r4E{mU zq-}D?+*2gAkQYAKrk*rB%4WFC-B!eZZLg4(tR#@kUQHIzEqV48$9=Q(~J_0 zy1%LSCbkoOhRO!J+Oh#;bGuXe;~(bIE*!J@i<%_IcB7wjhB5iF#jBn5+u~fEECN2* z!QFh!m<(>%49H12Y33+?$JxKV3xW{xSs=gxkxW-@Xds^|O1`AmorDKrE8N2-@ospk z=Au%h=f!`_X|G^A;XWL}-_L@D6A~*4Yf!5RTTm$!t8y&fp5_oqvBjW{FufS`!)5m% z2g(=9Ap6Y2y(9OYOWuUVGp-K=6kqQ)kM0P^TQT{X{V$*sN$wbFb-DaUuJF*!?EJPl zJev!UsOB^UHZ2KppYTELh+kqDw+5dPFv&&;;C~=u$Mt+Ywga!8YkL2~@g67}3wAQP zrx^RaXb1(c7vwU8a2se75X(cX^$M{FH4AHS7d2}heqqg4F0!1|Na>UtAdT%3JnS!B)&zelTEj$^b0>Oyfw=P-y-Wd^#dEFRUN*C{!`aJIHi<_YA2?piC%^ zj!p}+ZnBrM?ErAM+D97B*7L8U$K zo(IR-&LF(85p+fuct9~VTSdRjs`d-m|6G;&PoWvC&s8z`TotPSoksp;RsL4VL@CHf z_3|Tn%`ObgRhLmr60<;ya-5wbh&t z#ycN_)3P_KZN5CRyG%LRO4`Ot)3vY#dNX9!f!`_>1%4Q`81E*2BRg~A-VcN7pcX#j zrbl@7`V%n z6J53(m?KRzKb)v?iCuYWbH*l6M77dY4keS!%>}*8n!@ROE4!|7mQ+YS4dff1JJC(t z6Fnuf^=dajqHpH1=|pb(po9Fr8it^;2dEk|Ro=$fxqK$^Yix{G($0m-{RCFQJ~LqUnO7jJcjr zl*N*!6WU;wtF=dLCWzD6kW;y)LEo=4wSXQDIcq5WttgE#%@*m><@H;~Q&GniA-$in z`sjWFLgychS1kIJmPtd-w6%iKkj&dGhtB%0)pyy0M<4HZ@ZY0PWLAd7FCrj&i|NRh?>hZj*&FYnyu%Ur`JdiTu&+n z78d3n)Rl6q&NwVj_jcr#s5G^d?VtV8bkkYco5lV0LiT+t8}98LW>d)|v|V3++zLbHC(NC@X#Hx?21J0M*gP2V`Yd^DYvVIr{C zSc4V)hZKf|OMSm%FVqSRC!phWSyuUAu%0fredf#TDR$|hMZihJ__F!)Nkh6z)d=NC z3q4V*K3JTetxCPgB2_)rhOSWhuXzu+%&>}*ARxUaDeRy{$xK(AC0I=9%X7dmc6?lZNqe-iM(`?Xn3x2Ov>sej6YVQJ9Q42>?4lil?X zew-S>tm{=@QC-zLtg*nh5mQojYnvVzf3!4TpXPuobW_*xYJs;9AokrXcs!Ay z;HK>#;G$*TPN2M!WxdH>oDY6k4A6S>BM0Nimf#LfboKxJXVBC=RBuO&g-=+@O-#0m zh*aPG16zY^tzQLNAF7L(IpGPa+mDsCeAK3k=IL6^LcE8l0o&)k@?dz!79yxUquQIe($zm5DG z5RdXTv)AjHaOPv6z%99mPsa#8OD@9=URvHoJ1hYnV2bG*2XYBgB!-GEoP&8fLmWGg z9NG^xl5D&3L^io&3iYweV*qhc=m+r7C#Jppo$Ygg;jO2yaFU8+F*RmPL` zYxfGKla_--I}YUT353k}nF1zt2NO?+kofR8Efl$Bb^&llgq+HV_UYJUH7M5IoN0sT z4;wDA0gs55ZI|FmJ0}^Pc}{Ji-|#jdR$`!s)Di4^g3b_Qr<*Qu2rz}R6!B^;`Lj3sKWzjMYjexX)-;f5Y+HfkctE{PstO-BZan0zdXPQ=V8 zS8cBhnQyy4oN?J~oK0zl!#S|v6h-nx5to7WkdEk0HKBm;?kcNO*A+u=%f~l&aY*+J z>%^Dz`EQ6!+SEX$>?d(~|MNWU-}JTrk}&`IR|Ske(G^iMdk04)Cxd@}{1=P0U*%L5 zMFH_$R+HUGGv|ju2Z>5x(-aIbVJLcH1S+(E#MNe9g;VZX{5f%_|Kv7|UY-CM(>vf= z!4m?QS+AL+rUyfGJ;~uJGp4{WhOOc%2ybVP68@QTwI(8kDuYf?#^xv zBmOHCZU8O(x)=GVFn%tg@TVW1)qJJ_bU}4e7i>&V?r zh-03>d3DFj&@}6t1y3*yOzllYQ++BO-q!)zsk`D(z||)y&}o%sZ-tUF>0KsiYKFg6 zTONq)P+uL5Vm0w{D5Gms^>H1qa&Z##*X31=58*r%Z@Ko=IMXX{;aiMUp-!$As3{sq z0EEk02MOsgGm7$}E%H1ys2$yftNbB%1rdo@?6~0!a8Ym*1f;jIgfcYEF(I_^+;Xdr z2a>&oc^dF3pm(UNpazXgVzuF<2|zdPGjrNUKpdb$HOgNp*V56XqH`~$c~oSiqx;8_ zEz3fHoU*aJUbFJ&?W)sZB3qOSS;OIZ=n-*#q{?PCXi?Mq4aY@=XvlNQdA;yVC0Vy+ z{Zk6OO!lMYWd`T#bS8FV(`%flEA9El;~WjZKU1YmZpG#49`ku`oV{Bdtvzyz3{k&7 zlG>ik>eL1P93F zd&!aXluU_qV1~sBQf$F%sM4kTfGx5MxO0zJy<#5Z&qzNfull=k1_CZivd-WAuIQf> zBT3&WR|VD|=nKelnp3Q@A~^d_jN3@$x2$f@E~e<$dk$L@06Paw$);l*ewndzL~LuU zq`>vfKb*+=uw`}NsM}~oY}gW%XFwy&A>bi{7s>@(cu4NM;!%ieP$8r6&6jfoq756W z$Y<`J*d7nK4`6t`sZ;l%Oen|+pk|Ry2`p9lri5VD!Gq`U#Ms}pgX3ylAFr8(?1#&dxrtJgB>VqrlWZf61(r`&zMXsV~l{UGjI7R@*NiMJLUoK*kY&gY9kC@^}Fj* zd^l6_t}%Ku<0PY71%zQL`@}L}48M!@=r)Q^Ie5AWhv%#l+Rhu6fRpvv$28TH;N7Cl z%I^4ffBqx@Pxpq|rTJV)$CnxUPOIn`u278s9#ukn>PL25VMv2mff)-RXV&r`Dwid7}TEZxXX1q(h{R6v6X z&x{S_tW%f)BHc!jHNbnrDRjGB@cam{i#zZK*_*xlW@-R3VDmp)<$}S%t*@VmYX;1h zFWmpXt@1xJlc15Yjs2&e%)d`fimRfi?+fS^BoTcrsew%e@T^}wyVv6NGDyMGHSKIQ zC>qFr4GY?#S#pq!%IM_AOf`#}tPoMn7JP8dHXm(v3UTq!aOfEXNRtEJ^4ED@jx%le zvUoUs-d|2(zBsrN0wE(Pj^g5wx{1YPg9FL1)V1JupsVaXNzq4fX+R!oVX+q3tG?L= z>=s38J_!$eSzy0m?om6Wv|ZCbYVHDH*J1_Ndajoh&?L7h&(CVii&rmLu+FcI;1qd_ zHDb3Vk=(`WV?Uq;<0NccEh0s`mBXcEtmwt6oN99RQt7MNER3`{snV$qBTp={Hn!zz z1gkYi#^;P8s!tQl(Y>|lvz{5$uiXsitTD^1YgCp+1%IMIRLiSP`sJru0oY-p!FPbI)!6{XM%)(_Dolh1;$HlghB-&e><;zU&pc=ujpa-(+S&Jj zX1n4T#DJDuG7NP;F5TkoG#qjjZ8NdXxF0l58RK?XO7?faM5*Z17stidTP|a%_N z^e$D?@~q#Pf+708cLSWCK|toT1YSHfXVIs9Dnh5R(}(I;7KhKB7RD>f%;H2X?Z9eR z{lUMuO~ffT!^ew= z7u13>STI4tZpCQ?yb9;tSM-(EGb?iW$a1eBy4-PVejgMXFIV_Ha^XB|F}zK_gzdhM z!)($XfrFHPf&uyFQf$EpcAfk83}91Y`JFJOiQ;v5ca?)a!IxOi36tGkPk4S6EW~eq z>WiK`Vu3D1DaZ}515nl6>;3#xo{GQp1(=uTXl1~ z4gdWxr-8a$L*_G^UVd&bqW_nzMM&SlNW$8|$lAfo@zb+P>2q?=+T^qNwblP*RsN?N zdZE%^Zs;yAwero1qaoqMp~|KL=&npffh981>2om!fseU(CtJ=bW7c6l{U5(07*e0~ zJRbid6?&psp)ilmYYR3ZIg;t;6?*>hoZ3uq7dvyyq-yq$zH$yyImjfhpQb@WKENSP zl;KPCE+KXzU5!)mu12~;2trrLfs&nlEVOndh9&!SAOdeYd}ugwpE-9OF|yQs(w@C9 zoXVX`LP~V>%$<(%~tE*bsq(EFm zU5z{H@Fs^>nm%m%wZs*hRl=KD%4W3|(@j!nJr{Mmkl`e_uR9fZ-E{JY7#s6i()WXB0g-b`R{2r@K{2h3T+a>82>722+$RM*?W5;Bmo6$X3+Ieg9&^TU(*F$Q3 zT572!;vJeBr-)x?cP;^w1zoAM`nWYVz^<6N>SkgG3s4MrNtzQO|A?odKurb6DGZffo>DP_)S0$#gGQ_vw@a9JDXs2}hV&c>$ zUT0;1@cY5kozKOcbN6)n5v)l#>nLFL_x?2NQgurQH(KH@gGe>F|$&@ zq@2A!EXcIsDdzf@cWqElI5~t z4cL9gg7{%~4@`ANXnVAi=JvSsj95-7V& zME3o-%9~2?cvlH#twW~99=-$C=+b5^Yv}Zh4;Mg-!LS zw>gqc=}CzS9>v5C?#re>JsRY!w|Mtv#%O3%Ydn=S9cQarqkZwaM4z(gL~1&oJZ;t; zA5+g3O6itCsu93!G1J_J%Icku>b3O6qBW$1Ej_oUWc@MI)| zQ~eyS-EAAnVZp}CQnvG0N>Kc$h^1DRJkE7xZqJ0>p<>9*apXgBMI-v87E0+PeJ-K& z#(8>P_W^h_kBkI;&e_{~!M+TXt@z8Po*!L^8XBn{of)knd-xp{heZh~@EunB2W)gd zAVTw6ZZasTi>((qpBFh(r4)k zz&@Mc@ZcI-4d639AfcOgHOU+YtpZ)rC%Bc5gw5o~+E-i+bMm(A6!uE>=>1M;V!Wl4 z<#~muol$FsY_qQC{JDc8b=$l6Y_@_!$av^08`czSm!Xan{l$@GO-zPq1s>WF)G=wv zDD8j~Ht1pFj)*-b7h>W)@O&m&VyYci&}K|0_Z*w`L>1jnGfCf@6p}Ef*?wdficVe_ zmPRUZ(C+YJU+hIj@_#IiM7+$4kH#VS5tM!Ksz01siPc-WUe9Y3|pb4u2qnn zRavJiRpa zq?tr&YV?yKt<@-kAFl3s&Kq#jag$hN+Y%%kX_ytvpCsElgFoN3SsZLC>0f|m#&Jhu zp7c1dV$55$+k78FI2q!FT}r|}cIV;zp~#6X2&}22$t6cHx_95FL~T~1XW21VFuatb zpM@6w>c^SJ>Pq6{L&f9()uy)TAWf;6LyHH3BUiJ8A4}od)9sriz~e7}l7Vr0e%(=>KG1Jay zW0azuWC`(|B?<6;R)2}aU`r@mt_#W2VrO{LcX$Hg9f4H#XpOsAOX02x^w9+xnLVAt z^~hv2guE-DElBG+`+`>PwXn5kuP_ZiOO3QuwoEr)ky;o$n7hFoh}Aq0@Ar<8`H!n} zspCC^EB=6>$q*gf&M2wj@zzfBl(w_@0;h^*fC#PW9!-kT-dt*e7^)OIU{Uw%U4d#g zL&o>6`hKQUps|G4F_5AuFU4wI)(%9(av7-u40(IaI|%ir@~w9-rLs&efOR@oQy)}{ z&T#Qf`!|52W0d+>G!h~5A}7VJky`C3^fkJzt3|M&xW~x-8rSi-uz=qBsgODqbl(W#f{Ew#ui(K)(Hr&xqZs` zfrK^2)tF#|U=K|_U@|r=M_Hb;qj1GJG=O=d`~#AFAccecIaq3U`(Ds1*f*TIs=IGL zp_vlaRUtFNK8(k;JEu&|i_m39c(HblQkF8g#l|?hPaUzH2kAAF1>>Yykva0;U@&oRV8w?5yEK??A0SBgh?@Pd zJg{O~4xURt7!a;$rz9%IMHQeEZHR8KgFQixarg+MfmM_OeX#~#&?mx44qe!wt`~dd zqyt^~ML>V>2Do$huU<7}EF2wy9^kJJSm6HoAD*sRz%a|aJWz_n6?bz99h)jNMp}3k ztPVbos1$lC1nX_OK0~h>=F&v^IfgBF{#BIi&HTL}O7H-t4+wwa)kf3AE2-Dx@#mTA z!0f`>vz+d3AF$NH_-JqkuK1C+5>yns0G;r5ApsU|a-w9^j4c+FS{#+7- zH%skr+TJ~W_8CK_j$T1b;$ql_+;q6W|D^BNK*A+W5XQBbJy|)(IDA=L9d>t1`KX2b zOX(Ffv*m?e>! zS3lc>XC@IqPf1g-%^4XyGl*1v0NWnwZTW?z4Y6sncXkaA{?NYna3(n@(+n+#sYm}A zGQS;*Li$4R(Ff{obl3#6pUsA0fKuWurQo$mWXMNPV5K66V!XYOyc})^>889Hg3I<{V^Lj9($B4Zu$xRr=89-lDz9x`+I8q(vEAimx1K{sTbs|5x7S zZ+7o$;9&9>@3K;5-DVzGw=kp7ez%1*kxhGytdLS>Q)=xUWv3k_x(IsS8we39Tijvr z`GKk>gkZTHSht;5q%fh9z?vk%sWO}KR04G9^jleJ^@ovWrob7{1xy7V=;S~dDVt%S za$Q#Th%6g1(hiP>hDe}7lcuI94K-2~Q0R3A1nsb7Y*Z!DtQ(Ic<0;TDKvc6%1kBdJ z$hF!{uALB0pa?B^TC}#N5gZ|CKjy|BnT$7eaKj;f>Alqdb_FA3yjZ4CCvm)D&ibL) zZRi91HC!TIAUl<|`rK_6avGh`!)TKk=j|8*W|!vb9>HLv^E%t$`@r@piI(6V8pqDG zBON7~=cf1ZWF6jc{qkKm;oYBtUpIdau6s+<-o^5qNi-p%L%xAtn9OktFd{@EjVAT% z#?-MJ5}Q9QiK_jYYWs+;I4&!N^(mb!%4zx7qO6oCEDn=8oL6#*9XIJ&iJ30O`0vsFy|fEVkw}*jd&B6!IYi+~Y)qv6QlM&V9g0 zh)@^BVDB|P&#X{31>G*nAT}Mz-j~zd>L{v{9AxrxKFw8j;ccQ$NE0PZCc(7fEt1xd z`(oR2!gX6}R+Z77VkDz^{I)@%&HQT5q+1xlf*3R^U8q%;IT8-B53&}dNA7GW`Ki&= z$lrdH zDCu;j$GxW<&v_4Te7=AE2J0u1NM_7Hl9$u{z(8#%8vvrx2P#R7AwnY|?#LbWmROa; zOJzU_*^+n(+k;Jd{e~So9>OF>fPx$Hb$?~K1ul2xr>>o@**n^6IMu8+o3rDp(X$cC z`wQt9qIS>yjA$K~bg{M%kJ00A)U4L+#*@$8UlS#lN3YA{R{7{-zu#n1>0@(#^eb_% zY|q}2)jOEM8t~9p$X5fpT7BZQ1bND#^Uyaa{mNcFWL|MoYb@>y`d{VwmsF&haoJuS2W7azZU0{tu#Jj_-^QRc35tjW~ae&zhKk!wD}#xR1WHu z_7Fys#bp&R?VXy$WYa$~!dMxt2@*(>@xS}5f-@6eoT%rwH zv_6}M?+piNE;BqaKzm1kK@?fTy$4k5cqYdN8x-<(o6KelwvkTqC3VW5HEnr+WGQlF zs`lcYEm=HPpmM4;Ich7A3a5Mb3YyQs7(Tuz-k4O0*-YGvl+2&V(B&L1F8qfR0@vQM-rF<2h-l9T12eL}3LnNAVyY_z51xVr$%@VQ-lS~wf3mnHc zoM({3Z<3+PpTFCRn_Y6cbxu9v>_>eTN0>hHPl_NQQuaK^Mhrv zX{q#80ot;ptt3#js3>kD&uNs{G0mQp>jyc0GG?=9wb33hm z`y2jL=J)T1JD7eX3xa4h$bG}2ev=?7f>-JmCj6){Upo&$k{2WA=%f;KB;X5e;JF3IjQBa4e-Gp~xv- z|In&Rad7LjJVz*q*+splCj|{7=kvQLw0F@$vPuw4m^z=B^7=A4asK_`%lEf_oIJ-O z{L)zi4bd#&g0w{p1$#I&@bz3QXu%Y)j46HAJKWVfRRB*oXo4lIy7BcVl4hRs<%&iQ zr|)Z^LUJ>qn>{6y`JdabfNNFPX7#3`x|uw+z@h<`x{J4&NlDjnknMf(VW_nKWT!Jh zo1iWBqT6^BR-{T=4Ybe+?6zxP_;A5Uo{}Xel%*=|zRGm1)pR43K39SZ=%{MDCS2d$~}PE-xPw4ZK6)H;Zc&0D5p!vjCn0wCe&rVIhchR9ql!p2`g0b@JsC^J#n_r*4lZ~u0UHKwo(HaHUJDHf^gdJhTdTW z3i7Zp_`xyKC&AI^#~JMVZj^9WsW}UR#nc#o+ifY<4`M+?Y9NTBT~p`ONtAFf8(ltr*ER-Ig!yRs2xke#NN zkyFcaQKYv>L8mQdrL+#rjgVY>Z2_$bIUz(kaqL}cYENh-2S6BQK-a(VNDa_UewSW` zMgHi<3`f!eHsyL6*^e^W7#l?V|42CfAjsgyiJsA`yNfAMB*lAsJj^K3EcCzm1KT zDU2+A5~X%ax-JJ@&7>m`T;;}(-e%gcYQtj}?ic<*gkv)X2-QJI5I0tA2`*zZRX(;6 zJ0dYfMbQ+{9Rn3T@Iu4+imx3Y%bcf2{uT4j-msZ~eO)5Z_T7NC|Nr3)|NWjomhv=E zXaVin)MY)`1QtDyO7mUCjG{5+o1jD_anyKn73uflH*ASA8rm+S=gIfgJ);>Zx*hNG z!)8DDCNOrbR#9M7Ud_1kf6BP)x^p(|_VWCJ+(WGDbYmnMLWc?O4zz#eiP3{NfP1UV z(n3vc-axE&vko^f+4nkF=XK-mnHHQ7>w05$Q}iv(kJc4O3TEvuIDM<=U9@`~WdKN* zp4e4R1ncR_kghW}>aE$@OOc~*aH5OOwB5U*Z)%{LRlhtHuigxH8KuDwvq5{3Zg{Vr zrd@)KPwVKFP2{rXho(>MTZZfkr$*alm_lltPob4N4MmhEkv`J(9NZFzA>q0Ch;!Ut zi@jS_=0%HAlN+$-IZGPi_6$)ap>Z{XQGt&@ZaJ(es!Po5*3}>R4x66WZNsjE4BVgn z>}xm=V?F#tx#e+pimNPH?Md5hV7>0pAg$K!?mpt@pXg6UW9c?gvzlNe0 z3QtIWmw$0raJkjQcbv-7Ri&eX6Ks@@EZ&53N|g7HU<;V1pkc&$3D#8k!coJ=^{=vf z-pCP;vr2#A+i#6VA?!hs6A4P@mN62XYY$#W9;MwNia~89i`=1GoFESI+%Mbrmwg*0 zbBq4^bA^XT#1MAOum)L&ARDXJ6S#G>&*72f50M1r5JAnM1p7GFIv$Kf9eVR(u$KLt z9&hQ{t^i16zL1c(tRa~?qr?lbSN;1k;%;p*#gw_BwHJRjcYPTj6>y-rw*dFTnEs95 z`%-AoPL!P16{=#RI0 zUb6#`KR|v^?6uNnY`zglZ#Wd|{*rZ(x&Hk8N6ob6mpX~e^qu5kxvh$2TLJA$M=rx zc!#ot+sS+-!O<0KR6+Lx&~zgEhCsbFY{i_DQCihspM?e z-V}HemMAvFzXR#fV~a=Xf-;tJ1edd}Mry@^=9BxON;dYr8vDEK<<{ zW~rg(ZspxuC&aJo$GTM!9_sXu(EaQJNkV9AC(ob#uA=b4*!Uf}B*@TK=*dBvKKPAF z%14J$S)s-ws9~qKsf>DseEW(ssVQ9__YNg}r9GGx3AJiZR@w_QBlGP>yYh0lQCBtf zx+G;mP+cMAg&b^7J!`SiBwC81M_r0X9kAr2y$0(Lf1gZK#>i!cbww(hn$;fLIxRf? z!AtkSZc-h76KGSGz%48Oe`8ZBHkSXeVb!TJt_VC>$m<#}(Z}!(3h631ltKb3CDMw^fTRy%Ia!b&at`^g7Ew-%WLT9(#V0OP9CE?uj62s>`GI3NA z!`$U+i<`;IQyNBkou4|-7^9^ylac-Xu!M+V5p5l0Ve?J0wTSV+$gYtoc=+Ve*OJUJ z$+uIGALW?}+M!J9+M&#bT=Hz@{R2o>NtNGu1yS({pyteyb>*sg4N`KAD?`u3F#C1y z2K4FKOAPASGZTep54PqyCG(h3?kqQQAxDSW@>T2d!n;9C8NGS;3A8YMRcL>b=<<%M zMiWf$jY;`Ojq5S{kA!?28o)v$;)5bTL<4eM-_^h4)F#eeC2Dj*S`$jl^yn#NjJOYT zx%yC5Ww@eX*zsM)P(5#wRd=0+3~&3pdIH7CxF_2iZSw@>kCyd z%M}$1p((Bidw4XNtk&`BTkU{-PG)SXIZ)yQ!Iol6u8l*SQ1^%zC72FP zLvG>_Z0SReMvB%)1@+et0S{<3hV@^SY3V~5IY(KUtTR{*^xJ^2NN{sIMD9Mr9$~(C$GLNlSpzS=fsbw-DtHb_T|{s z9OR|sx!{?F``H!gVUltY7l~dx^a(2;OUV^)7 z%@hg`8+r&xIxmzZ;Q&v0X%9P)U0SE@r@(lKP%TO(>6I_iF{?PX(bez6v8Gp!W_nd5 z<8)`1jcT)ImNZp-9rr4_1MQ|!?#8sJQx{`~7)QZ75I=DPAFD9Mt{zqFrcrXCU9MG8 zEuGcy;nZ?J#M3!3DWW?Zqv~dnN6ijlIjPfJx(#S0cs;Z=jDjKY|$w2s4*Xa1Iz953sN2Lt!Vmk|%ZwOOqj`sA--5Hiaq8!C%LV zvWZ=bxeRV(&%BffMJ_F~~*FdcjhRVNUXu)MS(S#67rDe%Ler=GS+WysC1I2=Bmbh3s6wdS}o$0 zz%H08#SPFY9JPdL6blGD$D-AaYi;X!#zqib`(XX*i<*eh+2UEPzU4}V4RlC3{<>-~ zadGA8lSm>b7Z!q;D_f9DT4i)Q_}ByElGl*Cy~zX%IzHp)@g-itZB6xM70psn z;AY8II99e6P2drgtTG5>`^|7qg`9MTp%T~|1N3tBqV}2zgow3TFAH{XPor0%=HrkXnKyxyozHlJ6 zd3}OWkl?H$l#yZqOzZbMI+lDLoH48;s10!m1!K87g;t}^+A3f3e&w{EYhVPR0Km*- zh5-ku$Z|Ss{2?4pGm(Rz!0OQb^_*N`)rW{z)^Cw_`a(_L9j=&HEJl(!4rQy1IS)>- zeTIr>hOii`gc(fgYF(cs$R8l@q{mJzpoB5`5r>|sG zBpsY}RkY(g5`bj~D>(;F8v*DyjX(#nVLSs>)XneWI&%Wo>a0u#4A?N<1SK4D}&V1oN)76 z%S>a2n3n>G`YY1>0Hvn&AMtMuI_?`5?4y3w2Hnq4Qa2YH5 zxKdfM;k467djL31Y$0kd9FCPbU=pHBp@zaIi`Xkd80;%&66zvSqsq6%aY)jZacfvw ztkWE{ZV6V2WL9e}Dvz|!d96KqVkJU@5ryp#rReeWu>mSrOJxY^tWC9wd0)$+lZc%{ zY=c4#%OSyQJvQUuy^u}s8DN8|8T%TajOuaY^)R-&8s@r9D`(Ic4NmEu)fg1f!u`xUb;9t#rM z>}cY=648@d5(9A;J)d{a^*ORdVtJrZ77!g~^lZ9@)|-ojvW#>)Jhe8$7W3mhmQh@S zU=CSO+1gSsQ+Tv=x-BD}*py_Ox@;%#hPb&tqXqyUW9jV+fonnuCyVw=?HR>dAB~Fg z^vl*~y*4|)WUW*9RC%~O1gHW~*tJb^a-j;ae2LRNo|0S2`RX>MYqGKB^_ng7YRc@! zFxg1X!VsvXkNuv^3mI`F2=x6$(pZdw=jfYt1ja3FY7a41T07FPdCqFhU6%o|Yb6Z4 zpBGa=(ao3vvhUv#*S{li|EyujXQPUV;0sa5!0Ut)>tPWyC9e0_9(=v*z`TV5OUCcx zT=w=^8#5u~7<}8Mepqln4lDv*-~g^VoV{(+*4w(q{At6d^E-Usa2`JXty++Oh~on^ z;;WHkJsk2jvh#N|?(2PLl+g!M0#z_A;(#Uy=TzL&{Ei5G9#V{JbhKV$Qmkm%5tn!CMA? z@hM=b@2DZWTQ6>&F6WCq6;~~WALiS#@{|I+ucCmD6|tBf&e;$_)%JL8$oIQ%!|Xih1v4A$=7xNO zZVz$G8;G5)rxyD+M0$20L$4yukA_D+)xmK3DMTH3Q+$N&L%qB)XwYx&s1gkh=%qGCCPwnwhbT4p%*3R)I}S#w7HK3W^E%4w z2+7ctHPx3Q97MFYB48HfD!xKKb(U^K_4)Bz(5dvwyl*R?)k;uHEYVi|{^rvh)w7}t z`tnH{v9nlVHj2ign|1an_wz0vO)*`3RaJc#;(W-Q6!P&>+@#fptCgtUSn4!@b7tW0&pE2Qj@7}f#ugu4*C)8_}AMRuz^WG zc)XDcOPQjRaGptRD^57B83B-2NKRo!j6TBAJntJPHNQG;^Oz}zt5F^kId~miK3J@l ztc-IKp6qL!?u~q?qfGP0I~$5gvq#-0;R(oLU@sYayr*QH95fnrYA*E|n%&FP@Cz`a zSdJ~(c@O^>qaO`m9IQ8sd8!L<+)GPJDrL7{4{ko2gWOZel^3!($Gjt|B&$4dtfTmBmC>V`R&&6$wpgvdmns zxcmfS%9_ZoN>F~azvLFtA(9Q5HYT#A(byGkESnt{$Tu<73$W~reB4&KF^JBsoqJ6b zS?$D7DoUgzLO-?P`V?5_ub$nf1p0mF?I)StvPomT{uYjy!w&z$t~j&en=F~hw|O(1 zlV9$arQmKTc$L)Kupwz_zA~deT+-0WX6NzFPh&d+ly*3$%#?Ca9Z9lOJsGVoQ&1HNg+)tJ_sw)%oo*DK)iU~n zvL``LqTe=r=7SwZ@LB)9|3QB5`0(B9r(iR}0nUwJss-v=dXnwMRQFYSRK1blS#^g(3@z{`=8_CGDm!LESTWig zzm1{?AG&7`uYJ;PoFO$o8RWuYsV26V{>D-iYTnvq7igWx9@w$EC*FV^vpvDl@i9yp zPIqiX@hEZF4VqzI3Y)CHhR`xKN8poL&~ak|wgbE4zR%Dm(a@?bw%(7(!^>CM!^4@J z6Z)KhoQP;WBq_Z_&<@i2t2&xq>N>b;Np2rX?yK|-!14iE2T}E|jC+=wYe~`y38g3J z8QGZquvqBaG!vw&VtdXWX5*i5*% zJP~7h{?&E|<#l{klGPaun`IgAJ4;RlbRqgJz5rmHF>MtJHbfqyyZi53?Lhj=(Ku#& z__ubmZIxzSq3F90Xur!1)Vqe6b@!ueHA!93H~jdHmaS5Q^CULso}^poy)0Op6!{^9 zWyCyyIrdBP4fkliZ%*g+J-A!6VFSRF6Liu6G^^=W>cn81>4&7(c7(6vCGSAJ zQZ|S3mb|^Wf=yJ(h~rq`iiW~|n#$+KcblIR<@|lDtm!&NBzSG-1;7#YaU+-@=xIm4 zE}edTYd~e&_%+`dIqqgFntL-FxL3!m4yTNt<(^Vt9c6F(`?9`u>$oNxoKB29<}9FE zgf)VK!*F}nW?}l95%RRk8N4^Rf8)Xf;drT4<|lUDLPj^NPMrBPL;MX&0oGCsS za3}vWcF(IPx&W6{s%zwX{UxHX2&xLGfT{d9bWP!g;Lg#etpuno$}tHoG<4Kd*=kpU z;4%y(<^yj(UlG%l-7E9z_Kh2KoQ19qT3CR@Ghr>BAgr3Vniz3LmpC4g=g|A3968yD2KD$P7v$ zx9Q8`2&qH3&y-iv0#0+jur@}k`6C%7fKbCr|tHX2&O%r?rBpg`YNy~2m+ z*L7dP$RANzVUsG_Lb>=__``6vA*xpUecuGsL+AW?BeSwyoQfDlXe8R1*R1M{0#M?M zF+m19`3<`gM{+GpgW^=UmuK*yMh3}x)7P738wL8r@(Na6%ULPgbPVTa6gh5Q(SR0f znr6kdRpe^(LVM;6Rt(Z@Lsz3EX*ry6(WZ?w>#ZRelx)N%sE+MN>5G|Z8{%@b&D+Ov zPU{shc9}%;G7l;qbonIb_1m^Qc8ez}gTC-k02G8Rl?7={9zBz8uRX2{XJQ{vZhs67avlRn| zgRtWl0Lhjet&!YC47GIm%1gdq%T24_^@!W3pCywc89X4I5pnBCZDn(%!$lOGvS*`0!AoMtqxNPFgaMR zwoW$p;8l6v%a)vaNsesED3f}$%(>zICnoE|5JwP&+0XI}JxPccd+D^gx`g`=GsUc0 z9Uad|C+_@_0%JmcObGnS@3+J^0P!tg+fUZ_w#4rk#TlJYPXJiO>SBxzs9(J;XV9d{ zmTQE1(K8EYaz9p^XLbdWudyIPJlGPo0U*)fAh-jnbfm@SYD_2+?|DJ-^P+ojG{2{6 z>HJtedEjO@j_tqZ4;Zq1t5*5cWm~W?HGP!@_f6m#btM@46cEMhhK{(yI&jG)fwL1W z^n_?o@G8a-jYt!}$H*;{0#z8lANlo!9b@!c5K8<(#lPlpE!z86Yq#>WT&2} z;;G1$pD%iNoj#Z=&kij5&V1KHIhN-h<;{HC5wD)PvkF>CzlQOEx_0;-TJ*!#&{Wzt zKcvq^SZIdop}y~iouNqtU7K7+?eIz-v_rfNM>t#i+dD$s_`M;sjGubTdP)WI*uL@xPOLHt#~T<@Yz>xt50ZoTw;a(a}lNiDN-J${gOdE zx?8LOA|tv{Mb}=TTR=LcqMqbCJkKj+@;4Mu)Cu0{`~ohix6E$g&tff)aHeUAQQ%M? zIN4uSUTzC1iMEWL*W-in1y)C`E+R8j?4_?X4&2Zv5?QdkNMz(k} zw##^Ikx`#_s>i&CO_mu@vJJ*|3ePRDl5pq$9V^>D;g0R%l>lw;ttyM6Sy`NBF{)Lr zSk)V>mZr96+aHY%vTLLt%vO-+juw6^SO_ zYGJaGeWX6W(TOQx=5oTGXOFqMMU*uZyt>MR-Y`vxW#^&)H zk0!F8f*@v6NO@Z*@Qo)+hlX40EWcj~j9dGrLaq%1;DE_%#lffXCcJ;!ZyyyZTz74Q zb2WSly6sX{`gQeToQsi1-()5EJ1nJ*kXGD`xpXr~?F#V^sxE3qSOwRSaC9x9oa~jJ zTG9`E|q zC5Qs1xh}jzb5UPYF`3N9YuMnI7xsZ41P;?@c|%w zl=OxLr6sMGR+`LStLvh)g?fA5p|xbUD;yFAMQg&!PEDYxVYDfA>oTY;CFt`cg?Li1 z0b})!9Rvw&j#*&+D2))kXLL z0+j=?7?#~_}N-qdEIP>DQaZh#F(#e0WNLzwUAj@r694VJ8?Dr5_io2X49XYsG^ zREt0$HiNI~6VV!ycvao+0v7uT$_ilKCvsC+VDNg7yG1X+eNe^3D^S==F3ByiW0T^F zH6EsH^}Uj^VPIE&m)xlmOScYR(w750>hclqH~~dM2+;%GDXT`u4zG!p((*`Hwx41M z4KB+`hfT(YA%W)Ve(n+Gu9kuXWKzxg{1ff^xNQw>w%L-)RySTk9kAS92(X0Shg^Q? zx1YXg_TLC^?h6!4mBqZ9pKhXByu|u~gF%`%`vdoaGBN3^j4l!4x?Bw4Jd)Z4^di}! zXlG1;hFvc>H?bmmu1E7Vx=%vahd!P1#ZGJOJYNbaek^$DHt`EOE|Hlij+hX>ocQFSLVu|wz`|KVl@Oa;m2k6b*mNK2Vo{~l9>Qa3@B7G7#k?)aLx;w6U ze8bBq%vF?5v>#TspEoaII!N}sRT~>bh-VWJ7Q*1qsz%|G)CFmnttbq$Ogb{~YK_=! z{{0vhlW@g!$>|}$&4E3@k`KPElW6x#tSX&dfle>o!irek$NAbDzdd2pVeNzk4&qgJ zXvNF0$R96~g0x+R1igR=Xu&X_Hc5;!Ze&C)eUTB$9wW&?$&o8Yxhm5s(S`;?{> z*F?9Gr0|!OiKA>Rq-ae=_okB6&yMR?!JDer{@iQgIn=cGxs-u^!8Q$+N&pfg2WM&Z zulHu=Uh~U>fS{=Nm0x>ACvG*4R`Dx^kJ65&Vvfj`rSCV$5>c04N26Rt2S?*kh3JKq z9(3}5T?*x*AP(X2Ukftym0XOvg~r6Ms$2x&R&#}Sz23aMGU&7sU-cFvE3Eq`NBJe84VoftWF#v7PDAp`@V zRFCS24_k~;@~R*L)eCx@Q9EYmM)Sn}HLbVMyxx%{XnMBDc-YZ<(DXDBYUt8$u5Zh} zBK~=M9cG$?_m_M61YG+#|9Vef7LfbH>(C21&aC)x$^Lg}fa#SF){RX|?-xZjSOrn# z2ZAwUF)$VB<&S;R3FhNSQOV~8w%A`V9dWyLiy zgt7G=Z4t|zU3!dh5|s(@XyS|waBr$>@=^Dspmem8)@L`Ns{xl%rGdX!R(BiC5C7Vo zXetb$oC_iXS}2x_Hy}T(hUUNbO47Q@+^4Q`h>(R-;OxCyW#eoOeC51jzxnM1yxBrp zz6}z`(=cngs6X05e79o_B7@3K|Qpe3n38Py_~ zpi?^rj!`pq!7PHGliC$`-8A^Ib?2qgJJCW+(&TfOnFGJ+@-<<~`7BR0f4oSINBq&R z2CM`0%WLg_Duw^1SPwj-{?BUl2Y=M4e+7yL1{C&&f&zjF06#xf>VdLozgNye(BNgSD`=fFbBy0HIosLl@JwCQl^s;eTnc( z3!r8G=K>zb`|bLLI0N|eFJk%s)B>oJ^M@AQzqR;HUjLsOqW<0v>1ksT_#24*U@R3HJu*A^#1o#P3%3_jq>icD@<`tqU6ICEgZrME(xX#?i^Z z%Id$_uyQGlFD-CcaiRtRdGn|K`Lq5L-rx7`vYYGH7I=eLfHRozPiUtSe~Tt;IN2^gCXmf2#D~g2@9bhzK}3nphhG%d?V7+Zq{I2?Gt*!NSn_r~dd$ zqkUOg{U=MI?Ehx@`(X%rQB?LP=CjJ*V!rec{#0W2WshH$X#9zep!K)tzZoge*LYd5 z@g?-j5_mtMp>_WW`p*UNUZTFN{_+#m*bJzt{hvAdkF{W40{#L3w6gzPztnsA_4?&0 z(+>pv!zB16rR-(nm(^c>Z(its{ny677vT8sF564^mlZvJ!h65}OW%Hn|2OXbOQM%b z{6C54Z2v;^hyMQ;UH+HwFD2!F!VlQ}6Z{L0_9g5~CH0@Mqz?ZC`^QkhOU#$Lx<4`B zyZsa9uPF!rZDo8ZVfzzR#raQ>5|)k~_Ef*wDqG^76o)j!C4 zykvT*o$!-MBko@?{b~*Zf2*YMlImrK`cEp|#D7f%Twm<|C|dWD \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..6d57edc70 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..dd638c75a --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pubnub-kt' \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/vendor/Base64.java b/src/main/java/com/pubnub/api/vendor/Base64.java new file mode 100644 index 000000000..901562636 --- /dev/null +++ b/src/main/java/com/pubnub/api/vendor/Base64.java @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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 + * + * http://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 com.pubnub.api.vendor; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(Charset.forName("UTF-8")), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Non-data values in the DECODE arrays. + */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder(int flags, byte[] output) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3 / 4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p + 4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p + 1] & 0xff] << 12) | + (alphabet[input[p + 2] & 0xff] << 6) | + (alphabet[input[p + 3] & 0xff]))) >= 0) { + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op + 1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: + break; + case 1: + output_len += 2; + break; + case 2: + output_len += 3; + break; + default: + break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder(int flags, byte[] output) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8 / 5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p + 2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + case 2: + if (p + 1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p + 3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p + 1] & 0xff) << 8) | + (input[p + 2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op + 1] = alphabet[(v >> 12) & 0x3f]; + output[op + 2] = alphabet[(v >> 6) & 0x3f]; + output[op + 3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p - tailLen == len - 1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p - tailLen == len - 2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len - 1) { + tail[tailLen++] = input[p]; + } else if (p == len - 2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p + 1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { + } // don't instantiate +} diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt new file mode 100644 index 000000000..caf65be83 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -0,0 +1,275 @@ +package com.pubnub.api + +import com.google.gson.JsonElement +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.enums.PNStatusCategory.* +import com.pubnub.api.models.consumer.PNStatus +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.IOException +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.util.* + +abstract class Endpoint(internal val pubnub: PubNub) { + + + companion object { + private const val SERVER_RESPONSE_SUCCESS = 200 + private const val SERVER_RESPONSE_BAD_REQUEST = 400 + private const val SERVER_RESPONSE_FORBIDDEN = 403 + private const val SERVER_RESPONSE_NOT_FOUND = 404 + } + + // private lateinit var cachedCallback: PNCallback + private lateinit var cachedCallback: (result: Output?, status: PNStatus) -> Unit + private lateinit var call: Call + private var silenceFailures = false + + fun sync(): Output? { + validateParams() + + call = doWork(createBaseParams()) + + val response = + try { + call.execute() + } catch (e: IOException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.message + affectedCall = call + } + } + + when { + response.isSuccessful -> { + storeRequestLatency(response) + return createResponse(response) + } + else -> { + val (errorString, errorJson) = extractErrorBody(response) + throw PubNubException(PubNubError.HTTP_ERROR).apply { + errorMessage = errorString + jso = errorJson + statusCode = response.code() + affectedCall = call + } + } + } + } + + fun async(callback: (result: Output?, status: PNStatus) -> Unit) { + cachedCallback = callback + + try { + validateParams() + call = doWork(createBaseParams()) + } catch (pubnubException: PubNubException) { + callback.invoke( + null, + createStatusResponse( + category = PNBadRequestCategory, + exception = pubnubException + ) + ) + return + } + + call.enqueue(object : Callback { + + override fun onResponse(call: Call, response: Response) { + + when { + response.isSuccessful -> { + storeRequestLatency(response) + try { + Triple(PNAcknowledgmentCategory, createResponse(response), null) + } catch (e: PubNubException) { + Triple(PNMalformedResponseCategory, null, e) + }.let { + callback.invoke( + it.second, + createStatusResponse(it.first, response, it.third) + ) + } + } + else -> { + val (errorString, errorJson) = extractErrorBody(response) + + val exception = with(PubNubException(errorString)) { + jso = errorJson + statusCode = response.code() + this + } + val pnStatusCategory = when (response.code()) { + SERVER_RESPONSE_FORBIDDEN -> PNAccessDeniedCategory + SERVER_RESPONSE_BAD_REQUEST -> PNBadRequestCategory + SERVER_RESPONSE_NOT_FOUND -> PNNotFoundCategory + else -> PNUnknownCategory + } + callback.invoke( + null, + createStatusResponse( + pnStatusCategory, + response, + exception + ) + ) + return + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + if (silenceFailures) { + return + } + + lateinit var pnStatusCategory: PNStatusCategory + + val pubnubException = PubNubException(t.message) + + try { + throw t + } catch (networkException: UnknownHostException) { + pubnubException.pubnubError = PubNubError.CONNECTION_NOT_SET + pnStatusCategory = PNUnexpectedDisconnectCategory + } catch (connectException: ConnectException) { + pubnubException.pubnubError = PubNubError.CONNECT_EXCEPTION + pnStatusCategory = PNUnexpectedDisconnectCategory + } catch (socketTimeoutException: SocketTimeoutException) { + pubnubException.pubnubError = PubNubError.SUBSCRIBE_TIMEOUT + pnStatusCategory = PNTimeoutCategory + } catch (throwable1: Throwable) { + pubnubException.pubnubError = PubNubError.HTTP_ERROR + pnStatusCategory = if (call.isCanceled) { + PNCancelledCategory + } else { + PNBadRequestCategory + } + } + + callback.invoke( + null, + createStatusResponse( + category = pnStatusCategory, + exception = pubnubException + ) + ) + } + }) + } + + private fun storeRequestLatency(response: Response) { + // todo + println("storeRequestLatency: ${operationType().queryParam}") + } + + protected fun encodeParams(params: Map): Map { + val encodedParams = HashMap(params) + if (encodedParams.containsKey("auth")) { + encodedParams["auth"] = encodedParams["auth"]?.let { PubNubUtil.urlEncode(it) } + } + return encodedParams + } + + private fun createBaseParams(): HashMap { + val map = hashMapOf( + "pnsdk" to "PubNub-Kotlin/${pubnub.version}", + "uuid" to pubnub.config.uuid, + "instanceid" to pubnub.instanceId, + "requestid" to pubnub.requestId() + ) + if (isAuthRequired()) { + pubnub.config.authKey?.let { + map["auth"] = it + } + } + return map + } + + + private fun createStatusResponse( + category: PNStatusCategory, + response: Response? = null, + exception: PubNubException? = null + ): PNStatus { + + val pnStatus = PNStatus( + category = category, + error = response == null || exception != null, + operation = operationType(), + exception = exception + ) + + pnStatus.executedEndpoint = this + + response?.let { + + with(pnStatus) { + statusCode = it.code() + tlsEnabled = it.raw().request().url().isHttps + origin = it.raw().request().url().host() + uuid = it.raw().request().url().queryParameter("uuid") + authKey = it.raw().request().url().queryParameter("auth") + clientRequest = it.raw().request() + } + } + + + pnStatus.affectedChannels = getAffectedChannels() + pnStatus.affectedChannelGroups = getAffectedChannelGroups() + + return pnStatus + } + + internal fun retry() { + silenceFailures = false + async(cachedCallback) + } + + private fun extractErrorBody(response: Response): Pair { + val errorBodyString = try { + response.errorBody()?.string() + } catch (e: IOException) { + "N/A" + } + + val errorBodyJson = try { + pubnub.mapper.fromJson(errorBodyString, JsonElement::class.java) + } catch (e: PubNubException) { + null + } + + return errorBodyString to errorBodyJson + } + + protected abstract fun getAffectedChannels(): List + + protected abstract fun getAffectedChannelGroups(): List + + @Throws(PubNubException::class) + protected open fun validateParams() { + if (isSubKeyRequired() && pubnub.config.subscribeKey.isNullOrBlank()) { + throw PubNubException(PubNubError.SUBSCRIBE_KEY_MISSING) + } + if (isPubKeyRequired() && pubnub.config.publishKey.isNullOrBlank()) { + throw PubNubException(PubNubError.PUBLISH_KEY_MISSING) + } + } + + @Throws(PubNubException::class) + protected abstract fun doWork(queryParams: HashMap): Call + + @Throws(PubNubException::class) + protected abstract fun createResponse(input: Response): Output? + + protected abstract fun operationType(): PNOperationType + + protected abstract fun isSubKeyRequired(): Boolean + protected abstract fun isPubKeyRequired(): Boolean + protected abstract fun isAuthRequired(): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt new file mode 100644 index 000000000..4a3f1bdda --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt @@ -0,0 +1,82 @@ +package com.pubnub.api + +import com.pubnub.api.enums.PNHeartbeatNotificationOptions +import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.enums.PNReconnectionPolicy +import okhttp3.Authenticator +import okhttp3.CertificatePinner +import okhttp3.ConnectionSpec +import okhttp3.logging.HttpLoggingInterceptor +import java.net.Proxy +import java.net.ProxySelector +import java.util.* +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509ExtendedTrustManager + + +class PNConfiguration { + + companion object Constants { + private const val DEFAULT_DEDUPE_SIZE = 100 + private const val PRESENCE_TIMEOUT = 300 + private const val MINIMUM_PRESENCE_TIMEOUT = 20 + private const val NON_SUBSCRIBE_REQUEST_TIMEOUT = 10 + private const val SUBSCRIBE_TIMEOUT = 310 + private const val CONNECT_TIMEOUT = 5 + private const val DEFAULT_BASE_PATH = "ps.pndsn.com" + } + + var subscribeKey: String? = null + var publishKey: String? = null + var secretKey: String? = null + var uuid: String = "pn-${UUID.randomUUID()}" + var authKey: String? = null + var cipherKey: String? = null + + var origin = DEFAULT_BASE_PATH + var secure = true + + var logVerbosity = PNLogVerbosity.NONE + var heartbeatNotificationOptions = PNHeartbeatNotificationOptions.FAILURES + var reconnectionPolicy = PNReconnectionPolicy.NONE + + var presenceTimeout = PRESENCE_TIMEOUT + set(value) { + field = + if (value < MINIMUM_PRESENCE_TIMEOUT) { + println("Presence timeout is too low. Defaulting to: $MINIMUM_PRESENCE_TIMEOUT") + MINIMUM_PRESENCE_TIMEOUT + } else value + heartbeatInterval = (presenceTimeout / 2) - 1 + } + + var heartbeatInterval = 0 + + var subscribeTimeout = SUBSCRIBE_TIMEOUT + var connectTimeout = CONNECT_TIMEOUT + var nonSubscribeRequestTimeout = NON_SUBSCRIBE_REQUEST_TIMEOUT + var maximumMessagesCacheSize = DEFAULT_DEDUPE_SIZE + + var suppressLeaveEvents = false + var disableTokenManager = false + var filterExpression: String? = null + var includeInstanceIdentifier = false + var includeRequestIdentifier = true + var maximumReconnectionRetries = -1 + var maximumConnections: Int? = null + var requestMessageCountThreshold: Int? = null + var googleAppEngineNetworking = false + var startSubscriberThread = true + var dedupOnSubscribe = true + + var proxy: Proxy? = null + var proxySelector: ProxySelector? = null + var proxyAuthenticator: Authenticator? = null + var certificatePinner: CertificatePinner? = null + var httpLoggingInterceptor: HttpLoggingInterceptor? = null + var sslSocketFactory: SSLSocketFactory? = null + var x509ExtendedTrustManager: X509ExtendedTrustManager? = null + var connectionSpec: ConnectionSpec? = null + var hostnameVerifier: HostnameVerifier? = null +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt new file mode 100644 index 000000000..1c7c600c6 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -0,0 +1,49 @@ +package com.pubnub.api + +import com.pubnub.api.endpoints.History +import com.pubnub.api.endpoints.Publish +import com.pubnub.api.endpoints.Time +import com.pubnub.api.managers.BasePathManager +import com.pubnub.api.managers.MapperManager +import com.pubnub.api.managers.PublishSequenceManager +import com.pubnub.api.managers.RetrofitManager +import java.util.* + +class PubNub(val config: PNConfiguration) { + + companion object Constants { + private const val TIMESTAMP_DIVIDER = 1000 + private const val SDK_VERSION = "4.29.1" + } + + internal val mapper = MapperManager() + private val basePathManager = BasePathManager(this) + internal val retrofitManager = RetrofitManager(this) + internal val publishSequenceManager = PublishSequenceManager() + + val version = SDK_VERSION + val instanceId = UUID.randomUUID().toString() + + fun baseUrl() = basePathManager.basePath() + fun requestId() = UUID.randomUUID().toString() + fun timestamp() = Date().time / TIMESTAMP_DIVIDER + + inline fun time(function: Time.Params.() -> Unit): Time { + val time = Time(this) + time.params.function() + return time + } + + inline fun publish(function: Publish.Params.() -> Unit): Publish { + val publish = Publish(this) + publish.params.function() + return publish + } + + inline fun history(function: History.Params.() -> Unit): History { + val history = History(this) + history.params.function() + return history + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNubError.kt b/src/main/kotlin/com/pubnub/api/PubNubError.kt new file mode 100644 index 000000000..bcc255894 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/PubNubError.kt @@ -0,0 +1,70 @@ +package com.pubnub.api + +enum class PubNubError(val code: Int, val message: String) { + + TIMEOUT( + 100, + "Timeout Occurred" + ), + + JSON_ERROR( + 121, + "JSON Error while processing API response" + ), + + PARSING_ERROR( + 126, + "Parsing Error" + ), + + CONNECTION_NOT_SET( + 133, + "PubNub Connection not set" + ), + + SUBSCRIBE_KEY_MISSING( + 138, + "Subscribe Key not configured" + ), + + PUBLISH_KEY_MISSING( + 139, + "Publish Key not configured" + ), + + CONNECT_EXCEPTION( + 102, + "Connect Exception. Please verify if network is reachable" + ), + + SUBSCRIBE_TIMEOUT( + 130, + "Subscribe Timeout" + ), + + HTTP_ERROR( + 103, + "HTTP Error. Please check network connectivity. Please contact support with error details if the issue persists." + ), + + MESSAGE_MISSING( + 142, + "Message Missing" + ), + + CHANNEL_MISSING( + 132, + "Channel Missing" + ), + + CRYPTO_ERROR( + 135, + "Error while encrypting/decrypting message. Please contact support with error details." + ); + + override fun toString(): String { + return "PubNubError(name=$name, code=$code, message='$message')" + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNubException.kt b/src/main/kotlin/com/pubnub/api/PubNubException.kt new file mode 100644 index 000000000..d8536baf0 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/PubNubException.kt @@ -0,0 +1,19 @@ +package com.pubnub.api + +import com.google.gson.JsonElement +import retrofit2.Call + + +data class PubNubException( + var errorMessage: String? = null, + var pubnubError: PubNubError? = null, + var jso: JsonElement? = null, + var statusCode: Int = 0, + var affectedCall: Call<*>? = null +) : Exception(errorMessage) { + + constructor(pubnubError: PubNubError) : this( + errorMessage = pubnubError.message, + pubnubError = pubnubError + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt new file mode 100644 index 000000000..74fd5e8b2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -0,0 +1,28 @@ +package com.pubnub.api + +import java.io.UnsupportedEncodingException +import java.net.URLEncoder + +class PubNubUtil { + + companion object A { + + private const val CHARSET = "UTF-8" + + /** + * Returns encoded String + * + * @param stringToEncode , input string + * @return , encoded string + */ + fun urlEncode(stringToEncode: String): String { + return try { + URLEncoder.encode(stringToEncode, CHARSET).replace("+", "%20") + } catch (e: UnsupportedEncodingException) { + "" + } + + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/History.kt b/src/main/kotlin/com/pubnub/api/endpoints/History.kt new file mode 100644 index 000000000..07b1a284d --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/History.kt @@ -0,0 +1,145 @@ +package com.pubnub.api.endpoints + +import com.google.gson.JsonElement +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNHistoryItemResult +import com.pubnub.api.models.consumer.history.PNHistoryResult +import com.pubnub.api.vendor.Crypto +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class History(pubnub: PubNub) : Endpoint(pubnub) { + + companion object { + private const val MAX_COUNT = 100 + } + + inner class Params { + var channel: String? = null + var start: Long? = null + var end: Long? = null + var reverse = false + var count: Int? = null + var includeTimetoken = false + var includeMeta = false + } + + val params = Params() + + override fun validateParams() { + super.validateParams() + if (params.channel.isNullOrBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + } + + override fun getAffectedChannels() = listOf(params.channel) + + override fun getAffectedChannelGroups() = emptyList() + + override fun doWork(queryParams: HashMap): Call { + queryParams["reverse"] = params.reverse.toString() + queryParams["include_token"] = params.includeTimetoken.toString() + queryParams["include_meta"] = params.includeMeta.toString() + + params.count?.let { + queryParams["count"] = + when (it) { + in 1..MAX_COUNT -> it + else -> MAX_COUNT + }.toString() + } + + params.start?.let { + queryParams["start"] = it.toString().toLowerCase() + } + + params.end?.let { + queryParams["end"] = it.toString().toLowerCase() + } + + return pubnub.retrofitManager.historyService.fetchHistory( + pubnub.config.subscribeKey!!, + params.channel!!, + queryParams + ) + } + + override fun createResponse(input: Response): PNHistoryResult? { + + var result: PNHistoryResult? = null + val messages = ArrayList() + + input.body()?.let { + val startTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(it, 1)) + val endTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(it, 2)) + + pubnub.mapper.getArrayElement(it, 0).run { + if (this.isJsonArray) { + pubnub.mapper.getArrayIterator(this)?.forEach { + + var message: JsonElement? = null + var timetoken: Long? = null + var meta: JsonElement? = null + + if (params.includeTimetoken || params.includeMeta) { + if (params.includeTimetoken) { + timetoken = pubnub.mapper.elementToLong(it, "timetoken") + } + if (params.includeMeta) { + meta = pubnub.mapper.getField(it, "meta") + } + message = processMessage(pubnub.mapper.getField(it, "message")!!) + } else { + message = processMessage(it) + } + + messages += PNHistoryItemResult(message, timetoken, meta) + } + } + } + + result = PNHistoryResult(messages, startTimeToken, endTimeToken) + } + + return result + } + + @Throws(PubNubException::class) + private fun processMessage(message: JsonElement): JsonElement { + if (pubnub.config.cipherKey == null) { + return message + } + + val crypto = Crypto(pubnub.config.cipherKey!!) + + val inputText = pubnub.mapper.getField(message, "pn_other")?.let { + pubnub.mapper.elementToString(it) + }.run { + pubnub.mapper.elementToString(message) + } + + val outputText = crypto.decrypt(inputText!!) + + var outputObject = pubnub.mapper.fromJson(outputText, JsonElement::class.java) + + pubnub.mapper.getField(message, "pn_other")?.let { + val objectNode = pubnub.mapper.getAsObject(message) + pubnub.mapper.putOnObject(objectNode, "pn_other", outputObject) + outputObject = objectNode + } + + return outputObject + } + + override fun operationType() = PNOperationType.PNHistoryOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt b/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt new file mode 100644 index 000000000..a66676e37 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt @@ -0,0 +1,115 @@ +package com.pubnub.api.endpoints + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.vendor.Crypto +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { + + inner class Params { + var channel: String? = null + var message: Any? = null + var shouldStore: Boolean = false + var usePost: Boolean = false + var meta: Any? = null + var replicate = true + var ttl: Int? = null + } + + val params = Params() + + override fun validateParams() { + super.validateParams() + if (params.channel.isNullOrBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (params.message == null) { + throw PubNubException(PubNubError.MESSAGE_MISSING) + } + } + + override fun getAffectedChannels() = listOf(params.channel) + + override fun getAffectedChannelGroups() = emptyList() + + override fun doWork(queryParams: HashMap): Call> { + + var stringifiedMessage = pubnub.mapper.toJson(params.message!!) + + params.meta?.let { + val stringifiedMeta = pubnub.mapper.toJson(it) + PubNubUtil.urlEncode(stringifiedMeta) + queryParams["meta"] = stringifiedMeta + } + + + if (params.shouldStore) { + queryParams["store"] = "1" + } + + params.ttl?.let { + queryParams["ttl"] = it.toString() + } + + when { + !params.replicate -> queryParams["norep"] = "true" + } + + queryParams["seqn"] = pubnub.publishSequenceManager.nextSequence().toString() + + queryParams.putAll(encodeParams(queryParams)) + + pubnub.config.cipherKey?.let { + stringifiedMessage = Crypto(it).encrypt(stringifiedMessage).replace("\n", "") + } + + if (params.usePost) { + var payload = params.message + + pubnub.config.cipherKey?.let { + payload = stringifiedMessage + } + + return pubnub.retrofitManager.publishService.publishWithPost( + pubnub.config.publishKey!!, + pubnub.config.subscribeKey!!, + params.channel!!, + payload!!, + queryParams + ) + } else { + pubnub.config.cipherKey?.let { + stringifiedMessage = "\"$stringifiedMessage\"" + } + + stringifiedMessage = PubNubUtil.urlEncode(stringifiedMessage) + + return pubnub.retrofitManager.publishService.publish( + pubnub.config.publishKey!!, + pubnub.config.subscribeKey!!, + params.channel!!, + stringifiedMessage, + queryParams + ) + } + + + } + + override fun createResponse(input: Response>): PNPublishResult? { + return input.body()?.let { + PNPublishResult(it[2].toString().toLong()) + } + } + + override fun operationType() = PNOperationType.PNPublishOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = true + override fun isAuthRequired() = true + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Time.kt b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt new file mode 100644 index 000000000..732d6cec6 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt @@ -0,0 +1,34 @@ +package com.pubnub.api.endpoints + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNTimeResult +import retrofit2.Response + +class Time(pubnub: PubNub) : Endpoint, PNTimeResult>(pubnub) { + + inner class Params {} + + val params = Params() + + override fun getAffectedChannels() = emptyList() + + override fun getAffectedChannelGroups() = emptyList() + + override fun doWork(queryParams: HashMap) = + pubnub.retrofitManager.timeService.fetchTime(queryParams) + + override fun createResponse(input: Response>): PNTimeResult? { + return input.body()?.let { + PNTimeResult(it[0]) + } + } + + override fun operationType() = PNOperationType.PNTimeOperation + + override fun isAuthRequired() = false + override fun isSubKeyRequired() = false + override fun isPubKeyRequired() = false + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNHeartbeatNotificationOptions.kt b/src/main/kotlin/com/pubnub/api/enums/PNHeartbeatNotificationOptions.kt new file mode 100644 index 000000000..322145904 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNHeartbeatNotificationOptions.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.enums + +enum class PNHeartbeatNotificationOptions { + NONE, + FAILURES, + ALL +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNLogVerbosity.kt b/src/main/kotlin/com/pubnub/api/enums/PNLogVerbosity.kt new file mode 100644 index 000000000..583a0e34b --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNLogVerbosity.kt @@ -0,0 +1,6 @@ +package com.pubnub.api.enums + +enum class PNLogVerbosity { + NONE, + BODY, +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt b/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt new file mode 100644 index 000000000..292d5ef5a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt @@ -0,0 +1,75 @@ +package com.pubnub.api.enums; + +sealed class PNOperationType(open val queryParam: String? = null) { + + open class PublishOperation : PNOperationType("pub") + open class HistoryOperation : PNOperationType("hist") + open class PresenceOperation : PNOperationType("pres") + open class ChannelGroupOperation : PNOperationType("cg") + open class PushNotificationsOperation : PNOperationType("push") + open class PAMOperation : PNOperationType("pam") + open class MessageCountsOperation : PNOperationType("mc") + open class SignalsOperation : PNOperationType("sig") + open class ObjectsOperation : PNOperationType("obj") + open class PAMV3Operation : PNOperationType("pamv3") + open class MessageActionsOperation : PNOperationType("msga") + open class TimeOperation : PNOperationType("time") + + object PNSubscribeOperation : PNOperationType() + + object PNPublishOperation : PublishOperation() + + object PNHistoryOperation : HistoryOperation() + object PNFetchMessagesOperation : HistoryOperation() + object PNDeleteMessagesOperation : HistoryOperation() + + object PNUnsubscribeOperation : PresenceOperation() + object PNWhereNowOperation : PresenceOperation() + object PNHereNowOperation : PresenceOperation() + object PNHeartbeatOperation : PresenceOperation() + object PNSetStateOperation : PresenceOperation() + object PNGetState : PresenceOperation() + + object PNAddChannelsToGroupOperation : ChannelGroupOperation() + object PNRemoveChannelsFromGroupOperation : ChannelGroupOperation() + object PNChannelGroupsOperation : ChannelGroupOperation() + object PNRemoveGroupOperation : ChannelGroupOperation() + object PNChannelsForGroupOperation : ChannelGroupOperation() + + object PNPushNotificationEnabledChannelsOperation : PushNotificationsOperation() + object PNAddPushNotificationsOnChannelsOperation : PushNotificationsOperation() + object PNRemovePushNotificationsFromChannelsOperation : PushNotificationsOperation() + object PNRemoveAllPushNotificationsOperation : PushNotificationsOperation() + + object PNAccessManagerAudit : PAMOperation() + object PNAccessManagerGrant : PAMOperation() + + object PNMessageCountOperation : MessageCountsOperation() + + object PNSignalOperation : SignalsOperation() + + object PNCreateUserOperation : ObjectsOperation() + object PNGetUserOperation : ObjectsOperation() + object PNGetUsersOperation : ObjectsOperation() + object PNUpdateUserOperation : ObjectsOperation() + object PNDeleteUserOperation : ObjectsOperation() + object PNCreateSpaceOperation : ObjectsOperation() + object PNGetSpaceOperation : ObjectsOperation() + object PNGetSpacesOperation : ObjectsOperation() + object PNUpdateSpaceOperation : ObjectsOperation() + object PNDeleteSpaceOperation : ObjectsOperation() + object PNGetMembers : ObjectsOperation() + object PNManageMembers : ObjectsOperation() + object PNGetMemberships : ObjectsOperation() + object PNManageMemberships : ObjectsOperation() + + object PNAccessManagerGrantToken : PAMV3Operation() + + object PNAddMessageAction : MessageActionsOperation() + object PNGetMessageActions : MessageActionsOperation() + object PNDeleteMessageAction : MessageActionsOperation() + + object PNTimeOperation : TimeOperation() + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNReconnectionPolicy.kt b/src/main/kotlin/com/pubnub/api/enums/PNReconnectionPolicy.kt new file mode 100644 index 000000000..bc0bce39a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNReconnectionPolicy.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.enums + +enum class PNReconnectionPolicy { + NONE, + LINEAR, + EXPONENTIAL +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNStatusCategory.kt b/src/main/kotlin/com/pubnub/api/enums/PNStatusCategory.kt new file mode 100644 index 000000000..bb46a7695 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNStatusCategory.kt @@ -0,0 +1,24 @@ +package com.pubnub.api.enums + +enum class PNStatusCategory { + PNUnknownCategory, + PNAcknowledgmentCategory, + PNAccessDeniedCategory, + PNTimeoutCategory, + PNNetworkIssuesCategory, + PNConnectedCategory, + PNReconnectedCategory, + PNDisconnectedCategory, + PNUnexpectedDisconnectCategory, + PNCancelledCategory, + PNBadRequestCategory, + PNMalformedFilterExpressionCategory, + PNMalformedResponseCategory, + PNDecryptionErrorCategory, + PNTLSConnectionFailedCategory, + PNTLSUntrustedCertificateCategory, + + PNRequestMessageCountExceededCategory, + PNReconnectionAttemptsExhausted, + PNNotFoundCategory +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt new file mode 100644 index 000000000..284db649a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt @@ -0,0 +1,15 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PubNub + +class BasePathManager(val pubnub: PubNub) { + + fun basePath(): String { + return StringBuilder("http") + .append(if (pubnub.config.secure) "s" else "") + .append("://") + .append(pubnub.config.origin) + .toString() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt new file mode 100644 index 000000000..546db5d3e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -0,0 +1,114 @@ +package com.pubnub.api.managers + +import com.google.gson.* +import com.google.gson.internal.bind.TypeAdapters.* +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import retrofit2.Converter +import retrofit2.converter.gson.GsonConverterFactory + +class MapperManager { + + private val objectMapper: Gson + private val converterFactory: Converter.Factory + + init { + val booleanAsIntAdapter = object : TypeAdapter() { + override fun write(out: JsonWriter?, value: Boolean?) { + if (value == null) { + out?.nullValue() + } else { + out?.value(value) + } + } + + override fun read(_in: JsonReader?): Boolean? { + return when (_in?.peek()) { + BOOLEAN -> _in?.nextBoolean() + NUMBER -> _in?.nextInt() != 0 + STRING -> _in?.nextString()?.toBoolean() + else -> throw IllegalStateException("Expected BOOLEAN or NUMBER") + } + } + } + + objectMapper = GsonBuilder() + .registerTypeAdapter(Boolean::class.java, booleanAsIntAdapter) + .registerTypeAdapter(Boolean::class.javaPrimitiveType, booleanAsIntAdapter) + .create() + converterFactory = GsonConverterFactory.create(objectMapper) + } + + fun hasField(element: JsonElement, field: String) = element.asJsonObject.has(field) + + fun getField(element: JsonElement?, field: String): JsonElement? { + if (element?.isJsonObject!!) { + return element.asJsonObject.get(field) + } + return null + } + + fun getArrayIterator(element: JsonElement?) = element?.asJsonArray?.iterator() + + fun getArrayIterator(element: JsonElement, field: String) = element.asJsonObject.get(field).asJsonArray.iterator() + + fun getObjectIterator(element: JsonElement) = element.asJsonObject.entrySet().iterator() + + fun getObjectIterator(element: JsonElement, field: String) = + element.asJsonObject.get(field).asJsonObject.entrySet().iterator() + + fun elementToString(element: JsonElement) = element.asString + + fun elementToString(element: JsonElement, field: String) = element.asJsonObject.get(field).asString + + fun elementToInt(element: JsonElement, field: String) = element.asJsonObject.get(field).asInt + + fun isJsonObject(element: JsonElement) = element.isJsonObject + + fun getAsObject(element: JsonElement) = element.asJsonObject + + fun getAsBoolean(element: JsonElement, field: String) = element.asJsonObject.get(field).asBoolean + + fun putOnObject(element: JsonObject, key: String, value: JsonElement) = element.add(key, value) + + fun getArrayElement(element: JsonElement, index: Int) = element.asJsonArray.get(index) + + fun elementToLong(element: JsonElement) = element.asLong + + fun elementToLong(element: JsonElement, field: String) = element.asJsonObject.get(field).asLong + + fun getAsArray(element: JsonElement) = element.asJsonArray + + @Throws(PubNubException::class) + fun fromJson(input: String?, clazz: Class): T { + return try { + this.objectMapper.fromJson(input, clazz) + } catch (e: JsonParseException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.message + } + } + } + + fun convertValue(input: JsonElement, clazz: Class): T { + return this.objectMapper.fromJson(input, clazz) as T + } + + @Throws(PubNubException::class) + fun convertValue(obj: Any, clazz: Class): T { + return fromJson(toJson(obj), clazz) + } + + @Throws(PubNubException::class) + fun toJson(input: Any): String { + return try { + this.objectMapper.toJson(input) + } catch (e: JsonParseException) { + throw PubNubException(PubNubError.JSON_ERROR).apply { + errorMessage = e.message + } + } + } +} diff --git a/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt b/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt new file mode 100644 index 000000000..f32144bed --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.managers + +internal class PublishSequenceManager { + + private val maxSequence = 65535 + private var nextSequence = 0 + + @Synchronized + internal fun nextSequence(): Int { + if (maxSequence == nextSequence) { + nextSequence = 1 + } else { + nextSequence++ + } + return nextSequence + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt new file mode 100644 index 000000000..a95b7f898 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt @@ -0,0 +1,64 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.services.HistoryService +import com.pubnub.api.services.PublishService +import com.pubnub.api.services.TimeService +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + + +class RetrofitManager(val pubnub: PubNub) { + + private val transactionClientInstance = createOkHttpClient(pubnub.config.nonSubscribeRequestTimeout) + private val transactionInstance = createRetrofit(transactionClientInstance) + + val timeService: TimeService = transactionInstance.create(TimeService::class.java) + val publishService: PublishService = transactionInstance.create(PublishService::class.java) + val historyService: HistoryService = transactionInstance.create(HistoryService::class.java) + + private fun createOkHttpClient(readTimeout: Int): OkHttpClient { + val okHttpBuilder = OkHttpClient.Builder() + .readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) + .connectTimeout(pubnub.config.connectTimeout.toLong(), TimeUnit.SECONDS) + + with(pubnub.config) { + if (logVerbosity == PNLogVerbosity.BODY) { + okHttpBuilder.addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }) + } + if (sslSocketFactory != null && x509ExtendedTrustManager != null) { + okHttpBuilder.sslSocketFactory( + pubnub.config.sslSocketFactory!!, + pubnub.config.x509ExtendedTrustManager!! + ) + } + connectionSpec?.let { okHttpBuilder.connectionSpecs(listOf(it)) } + hostnameVerifier?.let { okHttpBuilder.hostnameVerifier(it) } + proxy?.let { okHttpBuilder.proxy(it) } + proxySelector?.let { okHttpBuilder.proxySelector(it) } + proxyAuthenticator?.let { okHttpBuilder.proxyAuthenticator(it) } + certificatePinner?.let { okHttpBuilder.certificatePinner(it) } + } + + val okHttpClient = okHttpBuilder.build() + + pubnub.config.maximumConnections?.let { okHttpClient.dispatcher().maxRequestsPerHost = it } + + return okHttpClient + } + + private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit { + val retrofitBuilder = Retrofit.Builder() + .client(okHttpClient) + .baseUrl(pubnub.baseUrl()) + .addConverterFactory(GsonConverterFactory.create()) + + return retrofitBuilder.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt new file mode 100644 index 000000000..d4c82fdff --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer + +/* +data class PNErrorData( + */ +/*val information: String?, + val throwable: Exception?*//* + + var information: String?, + var throwable: Exception? +) { +}*/ diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt new file mode 100644 index 000000000..6817815ea --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt @@ -0,0 +1,5 @@ +package com.pubnub.api.models.consumer + +data class PNPublishResult internal constructor( + val timetoken: Long +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt new file mode 100644 index 000000000..6f262e7d8 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt @@ -0,0 +1,34 @@ +package com.pubnub.api.models.consumer + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import okhttp3.Request + +data class PNStatus( + val category: PNStatusCategory, + val error: Boolean, + val operation: PNOperationType, + + val exception: PubNubException? = null, + + var statusCode: Int? = null, + var tlsEnabled: Boolean? = null, + var origin: String? = null, + var uuid: String? = null, + var authKey: String? = null, + + var affectedChannels: List = emptyList(), + var affectedChannelGroups: List = emptyList() + +) { + + var executedEndpoint: Endpoint<*, *>? = null + var clientRequest: Request? = null + + fun retry() { + executedEndpoint?.retry() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt new file mode 100644 index 000000000..245f0d78a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt @@ -0,0 +1,5 @@ +package com.pubnub.api.models.consumer + +data class PNTimeResult internal constructor( + val timetoken: Long +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt new file mode 100644 index 000000000..e160e52d2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.models.consumer.history + +import com.google.gson.JsonElement + +data class PNHistoryItemResult( + val entry: JsonElement, + val timetoken: Long? = null, + val meta: JsonElement? = null +) { + +} + + +/* +val timetoken: Long, +val entry: JsonElement, +val meta: JsonElement? = null*/ diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt new file mode 100644 index 000000000..491b433a4 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.history + +data class PNHistoryResult internal constructor( + val messages: List, + val startTimetoken: Long, + val endTimetoken: Long +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt new file mode 100644 index 000000000..50b0b8c02 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.services + +import com.google.gson.JsonElement +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +interface HistoryService { + + @GET("v2/history/sub-key/{subKey}/channel/{channel}") + fun fetchHistory( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap options: Map + ): Call +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PublishService.kt b/src/main/kotlin/com/pubnub/api/services/PublishService.kt new file mode 100644 index 000000000..5ecbe2f33 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/PublishService.kt @@ -0,0 +1,26 @@ +package com.pubnub.api.services + +import retrofit2.Call +import retrofit2.http.* + +interface PublishService { + + @GET("publish/{pubKey}/{subKey}/0/{channel}/0/{message}") + fun publish( + @Path("pubKey") pubKey: String, + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path(value = "message", encoded = true) message: String, + @QueryMap(encoded = true) options: Map + ): Call> + + @POST("publish/{pubKey}/{subKey}/0/{channel}/0") + @Headers("Content-Type: application/json; charset=UTF-8") + fun publishWithPost( + @Path("pubKey") pubKey: String, + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Body body: Any, + @QueryMap(encoded = true) options: Map + ): Call> +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/TimeService.kt b/src/main/kotlin/com/pubnub/api/services/TimeService.kt new file mode 100644 index 000000000..4aa343fbb --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/TimeService.kt @@ -0,0 +1,11 @@ +package com.pubnub.api.services + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.QueryMap + +interface TimeService { + + @GET("/time/0") + fun fetchTime(@QueryMap options: Map): Call> +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt b/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt new file mode 100644 index 000000000..066f3ccb4 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt @@ -0,0 +1,135 @@ +package com.pubnub.api.vendor + +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import java.io.UnsupportedEncodingException +import java.nio.charset.Charset +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import kotlin.experimental.and + +internal class Crypto(internal var cipherKey: String) { + + private var keyBytes: ByteArray? = null + private var ivBytes: ByteArray? = null + private var initializationVector = "0123456789012345" + private var initialized = false + + + @Throws(PubNubException::class) + fun initCiphers() { + if (initialized) + return + try { + + keyBytes = String(hexEncode(sha256(this.cipherKey.toByteArray(charset("UTF-8")))), charset("UTF-8")) + .substring(0, 32) + .toLowerCase().toByteArray(charset("UTF-8")) + ivBytes = initializationVector.toByteArray(charset("UTF-8")) + initialized = true + } catch (e: UnsupportedEncodingException) { + throw throwCryptoError(e, 11) + } + } + + @Throws(PubNubException::class) + fun hexEncode(input: ByteArray): ByteArray { + val result = StringBuffer() + input.forEach { + result.append(((it and 0xff.toByte()) + 0x100).toString(16).substring(1)) + } + try { + return result.toString().toByteArray(charset("UTF-8")) + } catch (e: UnsupportedEncodingException) { + throw throwCryptoError(e, 12) + } + } + + @Throws(PubNubException::class) + fun encrypt(input: String): String { + try { + initCiphers() + val ivSpec = IvParameterSpec(ivBytes) + val newKey = SecretKeySpec(keyBytes, "AES") + var cipher: Cipher? = null + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher!!.init(Cipher.ENCRYPT_MODE, newKey, ivSpec) + return String( + Base64.encode(cipher.doFinal(input.toByteArray(charset("UTF-8"))), 0), + Charset.forName("UTF-8") + ) + } catch (e: Exception) { + throw throwCryptoError(e) + } + } + + /** + * Decrypt + * + * @param cipher_text + * @return String + * @throws PubNubException + */ + @Throws(PubNubException::class) + fun decrypt(cipher_text: String): String { + try { + initCiphers() + val ivSpec = IvParameterSpec(ivBytes) + val newKey = SecretKeySpec(keyBytes, "AES") + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec) + return String(cipher.doFinal(Base64.decode(cipher_text, 0)), charset("UTF-8")) + } catch (e: Exception) { + throw throwCryptoError(e) + } + } + + /** + * Get MD5 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + @Throws(PubNubException::class) + fun md5(input: String): ByteArray { + val digest: MessageDigest + try { + digest = MessageDigest.getInstance("MD5") + return digest.digest(input.toByteArray(charset("UTF-8"))) + } catch (e: NoSuchAlgorithmException) { + throw throwCryptoError(e, 118) + } catch (e: UnsupportedEncodingException) { + throw throwCryptoError(e, 119) + } + + } + + /** + * Get SHA256 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + @Throws(PubNubException::class) + fun sha256(input: ByteArray): ByteArray { + val digest: MessageDigest + try { + digest = MessageDigest.getInstance("SHA-256") + return digest.digest(input) + } catch (e: NoSuchAlgorithmException) { + throw throwCryptoError(e, 111) + } + } + + private fun throwCryptoError(exception: Exception, code: Int? = null): PubNubException { + return PubNubException(PubNubError.CRYPTO_ERROR).apply { + errorMessage += " ${exception.message}, $code" + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/tests/AppTest.kt b/src/test/kotlin/com/pubnub/tests/AppTest.kt new file mode 100644 index 000000000..917a250ba --- /dev/null +++ b/src/test/kotlin/com/pubnub/tests/AppTest.kt @@ -0,0 +1,69 @@ +package com.pubnub.tests + +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNLogVerbosity +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.* + +class AppTest { + + lateinit var pubnub: PubNub + + @BeforeEach + fun initPubnub() { + pubnub = PubNub( + PNConfiguration().apply { + subscribeKey = "demo-36" + publishKey = "demo-36" + logVerbosity = PNLogVerbosity.BODY + } + ) + } + + @Test + fun testPublish() { + pubnub.publish { + channel = UUID.randomUUID().toString() + message = UUID.randomUUID().toString() + }.sync().let { + Assertions.assertNotNull(it) + } + } + + @Test + fun testPublishWithHistory() { + val expectedChannel = UUID.randomUUID().toString() + val expectedCount = 3 + val expectedMessages = generateSequence { UUID.randomUUID().toString() }.take(expectedCount).toList() + + expectedMessages.forEach { + pubnub.publish { + channel = expectedChannel + message = it + }.sync().let { + assertNotNull(it) + } + } + + Thread.sleep(1000) + + pubnub.history { + channel = expectedChannel + }.sync().let { result -> + assertNotNull(result) + assertEquals(expectedCount, result!!.messages.size) + + assertEquals(result.messages.map { it.entry.asString }, expectedMessages) + + result.messages.forEach { + expectedMessages.contains(it.entry.asString) + } + } + + } +} \ No newline at end of file From b9431136bf128d944012c72e6cf9bc2f7732c515 Mon Sep 17 00:00:00 2001 From: QSD Date: Fri, 1 Nov 2019 13:16:28 +0100 Subject: [PATCH 02/98] Initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..a42089d1d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# kotlin From 3a7d581605b06804405e036f0c746814834fba42 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:14:37 +0100 Subject: [PATCH 03/98] Add data models --- .../com/pubnub/api/models/SubscriptionItem.kt | 6 +++ .../api/models/consumer/PNSignalResult.kt | 10 +++++ .../pubnub/api/models/consumer/PNStatus.kt | 5 ++- .../consumer/history/PNHistoryItemResult.kt | 10 +---- .../message_actions/PNMessageAction.kt | 20 +++++++++ .../consumer/presence/PNGetStateResult.kt | 7 +++ .../api/models/consumer/presence/PNHereNow.kt | 20 +++++++++ .../consumer/presence/PNSetStateResult.kt | 7 +++ .../models/consumer/presence/PNWhereNow.kt | 5 +++ .../consumer/pubsub/BasePubSubResult.kt | 20 +++++++++ .../models/consumer/pubsub/MessageResult.kt | 8 ++++ .../models/consumer/pubsub/PNMessageResult.kt | 8 ++++ .../consumer/pubsub/PNPresenceEventResult.kt | 19 ++++++++ .../models/consumer/pubsub/PNSignalResult.kt | 8 ++++ .../message_actions/PNMessageActionResult.kt | 16 +++++++ .../consumer/pubsub/objects/ObjectPayload.kt | 11 +++++ .../consumer/pubsub/objects/ObjectResult.kt | 9 ++++ .../pubsub/objects/PNMembershipResult.kt | 14 ++++++ .../consumer/pubsub/objects/PNSpaceResult.kt | 14 ++++++ .../consumer/pubsub/objects/PNUserResult.kt | 14 ++++++ .../com/pubnub/api/models/server/Envelope.kt | 14 ++++++ .../api/models/server/OriginationMetaData.kt | 11 +++++ .../api/models/server/PresenceEnvelope.kt | 11 +++++ .../api/models/server/PublishMetaData.kt | 12 +++++ .../api/models/server/SubscribeEnvelope.kt | 12 +++++ .../api/models/server/SubscribeMessage.kt | 44 +++++++++++++++++++ .../api/models/server/SubscribeMetaData.kt | 11 +++++ .../models/server/presence/WhereNowPayload.kt | 3 ++ 28 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/com/pubnub/api/models/SubscriptionItem.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/MessageResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNSignalResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/Envelope.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/OriginationMetaData.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/PresenceEnvelope.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/PublishMetaData.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/SubscribeEnvelope.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/SubscribeMessage.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/SubscribeMetaData.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/presence/WhereNowPayload.kt diff --git a/src/main/kotlin/com/pubnub/api/models/SubscriptionItem.kt b/src/main/kotlin/com/pubnub/api/models/SubscriptionItem.kt new file mode 100644 index 000000000..f59086069 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/SubscriptionItem.kt @@ -0,0 +1,6 @@ +package com.pubnub.api.models + +internal class SubscriptionItem( + internal var name: String, + internal var state: Any? = null +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt new file mode 100644 index 000000000..a7675e611 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer + +import com.google.gson.JsonElement +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult +import com.pubnub.api.models.consumer.pubsub.MessageResult + +class PNSignalResult( + basePubSubResult: BasePubSubResult, + message: JsonElement +) : MessageResult(basePubSubResult, message) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt index 6f262e7d8..fa14136c3 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt @@ -7,8 +7,8 @@ import com.pubnub.api.enums.PNStatusCategory import okhttp3.Request data class PNStatus( - val category: PNStatusCategory, - val error: Boolean, + var category: PNStatusCategory, + var error: Boolean, val operation: PNOperationType, val exception: PubNubException? = null, @@ -24,6 +24,7 @@ data class PNStatus( ) { + var executedEndpoint: Endpoint<*, *>? = null var clientRequest: Request? = null diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt index e160e52d2..11257526e 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt @@ -6,12 +6,4 @@ data class PNHistoryItemResult( val entry: JsonElement, val timetoken: Long? = null, val meta: JsonElement? = null -) { - -} - - -/* -val timetoken: Long, -val entry: JsonElement, -val meta: JsonElement? = null*/ +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt new file mode 100644 index 000000000..2f06b46e2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt @@ -0,0 +1,20 @@ +package com.pubnub.api.models.consumer.message_actions + +class PNMessageAction { + val type: String + val value: String + val messageTimetoken: Long + + constructor(pnMessageAction: PNMessageAction) { + this.type = pnMessageAction.type + this.value = pnMessageAction.value + this.messageTimetoken = pnMessageAction.messageTimetoken + this.uuid = pnMessageAction.uuid + this.actionTimetoken = pnMessageAction.actionTimetoken + } + + var uuid: String + private set + var actionTimetoken: Long? = null + private set +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt new file mode 100644 index 000000000..a4c83e2b1 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.presence + +import com.google.gson.JsonElement + +data class PNGetStateResult( + val stateByUUID: Map +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt new file mode 100644 index 000000000..fc49951ec --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt @@ -0,0 +1,20 @@ +package com.pubnub.api.models.consumer.presence + +import com.google.gson.JsonElement + +data class PNHereNowResult( + val totalChannels: Int, + val totalOccupancy: Int, + var channels: HashMap = hashMapOf() +) + +data class PNHereNowChannelData( + val channelName: String, + val occupancy: Int, + var occupants: List = emptyList() +) + +data class PNHereNowOccupantData( + val uuid: String, + val state: JsonElement? = null +) diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt new file mode 100644 index 000000000..05b5ba784 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.presence + +import com.google.gson.JsonElement + +data class PNSetStateResult( + val state: JsonElement? +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt new file mode 100644 index 000000000..78ea70228 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt @@ -0,0 +1,5 @@ +package com.pubnub.api.models.consumer.presence + +data class PNWhereNowResult( + val channels: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt new file mode 100644 index 000000000..2a7e1aaa0 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt @@ -0,0 +1,20 @@ +package com.pubnub.api.models.consumer.pubsub + +import com.google.gson.JsonElement + +open class BasePubSubResult( + internal val channel: String, + internal val subscription: String?, + internal val timetoken: Long?, + internal val userMetadata: JsonElement?, + internal val publisher: String? + +) { + constructor(basePubSubResult: BasePubSubResult) : this( + basePubSubResult.channel, + basePubSubResult.subscription, + basePubSubResult.timetoken, + basePubSubResult.userMetadata, + basePubSubResult.publisher + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/MessageResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/MessageResult.kt new file mode 100644 index 000000000..42af17567 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/MessageResult.kt @@ -0,0 +1,8 @@ +package com.pubnub.api.models.consumer.pubsub + +import com.google.gson.JsonElement + +open class MessageResult( + basePubSubResult: BasePubSubResult, + val message: JsonElement +) : BasePubSubResult(basePubSubResult) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt new file mode 100644 index 000000000..2fb2d06ab --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt @@ -0,0 +1,8 @@ +package com.pubnub.api.models.consumer.pubsub + +import com.google.gson.JsonElement + +data class PNMessageResult( + val basePubSubResult: BasePubSubResult, + val messageResult: JsonElement +) : MessageResult(basePubSubResult, messageResult) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt new file mode 100644 index 000000000..40a6f9b5e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.pubsub + +import com.google.gson.JsonElement + +data class PNPresenceEventResult( + internal val event: String? = null, + internal val uuid: String? = null, + internal val timestamp: Long? = null, + internal val occupancy: Int? = null, + internal val state: JsonElement? = null, + internal val channel: String? = null, + internal val subscription: String? = null, + internal val timetoken: Long? = null, + internal val join: List? = null, + internal val leave: List? = null, + internal val timeout: List? = null, + internal val hereNowRefresh: Boolean? = null, + internal val userMetadata: Any? = null +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNSignalResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNSignalResult.kt new file mode 100644 index 000000000..d5dc664f3 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNSignalResult.kt @@ -0,0 +1,8 @@ +package com.pubnub.api.models.consumer.pubsub + +import com.google.gson.JsonElement + +class PNSignalResult( + basePubSubResult: BasePubSubResult, + messageResult: JsonElement +) : MessageResult(basePubSubResult, messageResult) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt new file mode 100644 index 000000000..81d043cac --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.pubsub.message_actions + +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult +import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult + +class PNMessageActionResult( + result: BasePubSubResult, + event: String, + data: PNMessageAction +) : ObjectResult(result, event, data) { + + fun messageAction(): PNMessageAction { + return data + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.kt new file mode 100644 index 000000000..91e30c6ce --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.kt @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.pubsub.objects + +import com.google.gson.JsonElement + +data class ObjectPayload( + val source: String, + val version: String, + val event: String, + val type: String, + val data: JsonElement +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt new file mode 100644 index 000000000..37dc1967f --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt @@ -0,0 +1,9 @@ +package com.pubnub.api.models.consumer.pubsub.objects + +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult + +open class ObjectResult( + open val result: BasePubSubResult, + open val event: String, + open val data: T +) : BasePubSubResult(result) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt new file mode 100644 index 000000000..46b08dc73 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.pubsub.objects + +import com.google.gson.JsonElement +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult + +class PNMembershipResult( + override val result: BasePubSubResult, + override val event: String, + override val data: JsonElement +) : ObjectResult( + result, + event, + data +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt new file mode 100644 index 000000000..6dc4edd7f --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.pubsub.objects + +import com.google.gson.JsonElement +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult + +class PNSpaceResult( + override val result: BasePubSubResult, + override val event: String, + override val data: JsonElement +) : ObjectResult( + result, + event, + data +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt new file mode 100644 index 000000000..07b62aee4 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.pubsub.objects + +import com.google.gson.JsonElement +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult + +class PNUserResult( + override val result: BasePubSubResult, + override val event: String, + override val data: JsonElement +) : ObjectResult( + result, + event, + data +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/Envelope.kt b/src/main/kotlin/com/pubnub/api/models/server/Envelope.kt new file mode 100644 index 000000000..957d849ff --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/Envelope.kt @@ -0,0 +1,14 @@ +package com.pubnub.api.models.server + +import com.google.gson.JsonElement + +class Envelope { + internal val status: Int = 0 + internal val message: String? = null + internal val service: String? = null + internal val payload: T? = null + internal val occupancy: Int = 0 + internal val uuids: JsonElement? = null + internal val action: String? = null + internal val error: Boolean = false +} diff --git a/src/main/kotlin/com/pubnub/api/models/server/OriginationMetaData.kt b/src/main/kotlin/com/pubnub/api/models/server/OriginationMetaData.kt new file mode 100644 index 000000000..05a16eb43 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/OriginationMetaData.kt @@ -0,0 +1,11 @@ +package com.pubnub.api.models.server + +import com.google.gson.annotations.SerializedName + +class OriginationMetaData( + @SerializedName("t") + internal val timetoken: Long?, + + @SerializedName("r") + internal val region: Int? +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/PresenceEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/PresenceEnvelope.kt new file mode 100644 index 000000000..ffd92d2a6 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/PresenceEnvelope.kt @@ -0,0 +1,11 @@ +package com.pubnub.api.models.server + +import com.google.gson.JsonElement + +class PresenceEnvelope( + internal val action: String? = null, + internal val uuid: String? = null, + internal val occupancy: Int? = null, + internal val timestamp: Long? = null, + internal val data: JsonElement? = null +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/PublishMetaData.kt b/src/main/kotlin/com/pubnub/api/models/server/PublishMetaData.kt new file mode 100644 index 000000000..873c8d68b --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/PublishMetaData.kt @@ -0,0 +1,12 @@ +package com.pubnub.api.models.server + +import com.google.gson.annotations.SerializedName + +class PublishMetaData( + @SerializedName("t") + internal val publishTimetoken: Long?, + + @SerializedName("r") + internal val region: Int? +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/SubscribeEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/SubscribeEnvelope.kt new file mode 100644 index 000000000..b68070fdf --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/SubscribeEnvelope.kt @@ -0,0 +1,12 @@ +package com.pubnub.api.models.server + +import com.google.gson.annotations.SerializedName + +class SubscribeEnvelope( + + @SerializedName("m") + internal val messages: List, + + @SerializedName("t") + internal val metadata: SubscribeMetaData +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/SubscribeMessage.kt b/src/main/kotlin/com/pubnub/api/models/server/SubscribeMessage.kt new file mode 100644 index 000000000..d7ef19666 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/SubscribeMessage.kt @@ -0,0 +1,44 @@ +package com.pubnub.api.models.server + +import com.google.gson.JsonElement +import com.google.gson.annotations.SerializedName + +class SubscribeMessage( + @SerializedName("a") + internal val shard: String?, + + @SerializedName("b") + internal val subscriptionMatch: String?, + + @SerializedName("c") + internal val channel: String?, + + @SerializedName("d") + internal val payload: JsonElement?, + + @SerializedName("f") + internal val flags: String?, + + @SerializedName("i") + internal val issuingClientId: String?, + + @SerializedName("k") + internal val subscribeKey: String?, + + @SerializedName("o") + internal val originationMetadata: OriginationMetaData?, + + @SerializedName("p") + internal val publishMetaData: PublishMetaData?, + + @SerializedName("u") + internal val userMetadata: JsonElement?, + + @SerializedName("e") + internal val type: Int? + +) { + + fun supportsEncryption() = type == null || type == 0 + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/SubscribeMetaData.kt b/src/main/kotlin/com/pubnub/api/models/server/SubscribeMetaData.kt new file mode 100644 index 000000000..0c6a4d008 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/SubscribeMetaData.kt @@ -0,0 +1,11 @@ +package com.pubnub.api.models.server + +import com.google.gson.annotations.SerializedName + +class SubscribeMetaData( + @SerializedName("t") + internal val timetoken: Long, + + @SerializedName("r") + internal val region: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/presence/WhereNowPayload.kt b/src/main/kotlin/com/pubnub/api/models/server/presence/WhereNowPayload.kt new file mode 100644 index 000000000..fbcf4b94b --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/presence/WhereNowPayload.kt @@ -0,0 +1,3 @@ +package com.pubnub.api.models.server.presence + +class WhereNowPayload(val channels: List) \ No newline at end of file From 5897e980db91d4ff950839aa5dc25e7fc35e867e Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:15:46 +0100 Subject: [PATCH 04/98] Implement the subscribe loop --- .../com/pubnub/api/builder/PubSubBuilders.kt | 58 +++ .../pubnub/api/builder/PubSubOperations.kt | 25 ++ .../api/callbacks/ReconnectionCallback.kt | 6 + .../pubnub/api/callbacks/SubscribeCallback.kt | 29 ++ .../pubnub/api/endpoints/pubsub/Subscribe.kt | 80 ++++ .../com/pubnub/api/managers/StateManager.kt | 170 ++++++++ .../api/managers/SubscriptionManager.kt | 397 ++++++++++++++++++ 7 files changed, 765 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt create mode 100644 src/main/kotlin/com/pubnub/api/builder/PubSubOperations.kt create mode 100644 src/main/kotlin/com/pubnub/api/callbacks/ReconnectionCallback.kt create mode 100644 src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/StateManager.kt create mode 100644 src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt diff --git a/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt new file mode 100644 index 000000000..141cbed3f --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt @@ -0,0 +1,58 @@ +package com.pubnub.api.builder + +import com.pubnub.api.managers.SubscriptionManager + +abstract class PubSubBuilder( + protected val subscriptionManager: SubscriptionManager, + internal var channels: List = emptyList(), + internal var channelGroups: List = emptyList() +) { + abstract fun execute() +} + +internal class PresenceBuilder( + subscriptionManager: SubscriptionManager, + channels: List, + channelGroups: List, + internal var connected: Boolean = false +) : PubSubBuilder(subscriptionManager, channels, channelGroups) { + + override fun execute() { + val presenceOperation = PresenceOperation().apply { + connected = this@PresenceBuilder.connected + channels = this@PresenceBuilder.channels + channelGroups = this@PresenceBuilder.channelGroups + } + subscriptionManager.adaptPresenceBuilder(presenceOperation) + } +} + +class SubscribeBuilder( + subscriptionManager: SubscriptionManager, + var withPresence: Boolean = false, + var withTimetoken: Long = 0L +) : PubSubBuilder(subscriptionManager) { + + override fun execute() { + val subscribeOperation = SubscribeOperation().apply { + channels = this@SubscribeBuilder.channels + channelGroups = this@SubscribeBuilder.channelGroups + presenceEnabled = this@SubscribeBuilder.withPresence + timetoken = this@SubscribeBuilder.withTimetoken + } + this.subscriptionManager.adaptSubscribeBuilder(subscribeOperation) + } +} + +class UnsubscribeBuilder( + subscriptionManager: SubscriptionManager +) : PubSubBuilder(subscriptionManager) { + + override fun execute() { + val unsubscribeOperation = UnsubscribeOperation().apply { + channels = this@UnsubscribeBuilder.channels + channelGroups = this@UnsubscribeBuilder.channelGroups + } + this.subscriptionManager.adaptUnsubscribeBuilder(unsubscribeOperation) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/builder/PubSubOperations.kt b/src/main/kotlin/com/pubnub/api/builder/PubSubOperations.kt new file mode 100644 index 000000000..23259dfbc --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/builder/PubSubOperations.kt @@ -0,0 +1,25 @@ +package com.pubnub.api.builder + +// basic publish/subscribe class + +internal abstract class PubSubOperation( + internal var channels: List = emptyList(), + internal var channelGroups: List = emptyList() +) + +// concrete publish/subscribe cases + +internal class SubscribeOperation( + internal var presenceEnabled: Boolean = false, + internal var timetoken: Long = 0L +) : PubSubOperation() + +internal class UnsubscribeOperation : PubSubOperation() + +internal class PresenceOperation( + internal var connected: Boolean = false +) : PubSubOperation() + +internal class StateOperation( + var state: Any? = null +) : PubSubOperation() \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/callbacks/ReconnectionCallback.kt b/src/main/kotlin/com/pubnub/api/callbacks/ReconnectionCallback.kt new file mode 100644 index 000000000..79f01bfda --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/callbacks/ReconnectionCallback.kt @@ -0,0 +1,6 @@ +package com.pubnub.api.callbacks + +internal abstract class ReconnectionCallback { + abstract fun onReconnection() + abstract fun onMaxReconnectionExhaustion() +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt new file mode 100644 index 000000000..fa89e9b7e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt @@ -0,0 +1,29 @@ +package com.pubnub.api.callbacks + +import com.pubnub.api.PubNub +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult +import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult +import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult + +abstract class SubscribeCallback { + abstract fun status(pubnub: PubNub, pnStatus: PNStatus) + + abstract fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) + + abstract fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) + + abstract fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) + + abstract fun user(pubnub: PubNub, pnUserResult: PNUserResult) + + abstract fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) + + abstract fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) + + abstract fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt new file mode 100644 index 000000000..b551d6a0a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt @@ -0,0 +1,80 @@ +package com.pubnub.api.endpoints.pubsub + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.PubNubUtil +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.server.SubscribeEnvelope +import com.pubnub.api.toCsv +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +class Subscribe(pubnub: PubNub) : Endpoint(pubnub) { + + var channels = listOf() + var channelGroups = listOf() + var timetoken: Long? = null + var region: String? = null + var state: Any? = null + var filterExpression: String? = null + + override fun validateParams() { + super.validateParams() + if (channels.isEmpty() && channelGroups.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_AND_GROUP_MISSING) + } + } + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = channelGroups + + override fun doWork(queryParams: HashMap): Call { + if (channelGroups.isNotEmpty()) { + queryParams["channel-group"] = channelGroups.joinToString(",") + } + + if (!filterExpression.isNullOrBlank()) { + queryParams["filter-expr"] = PubNubUtil.urlEncode(filterExpression!!) + } + + timetoken?.let { + queryParams["tt"] = it.toString() + } + + region?.let { + queryParams["tr"] = it + } + + queryParams["heartbeat"] = pubnub.configuration.presenceTimeout.toString() + + state?.let { + queryParams["state"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(it)) + } + + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.subscribeService.subscribe( + pubnub.configuration.subscribeKey, + channels.toCsv(), + queryParams + ) + } + + override fun createResponse(input: Response): SubscribeEnvelope? { + if (input.body() == null) { + throw PubNubException(PubNubError.PARSING_ERROR) + } else { + return input.body() + } + } + + override fun operationType() = PNOperationType.PNSubscribeOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/StateManager.kt b/src/main/kotlin/com/pubnub/api/managers/StateManager.kt new file mode 100644 index 000000000..47c583f36 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/StateManager.kt @@ -0,0 +1,170 @@ +package com.pubnub.api.managers + +import com.pubnub.api.builder.PresenceOperation +import com.pubnub.api.builder.StateOperation +import com.pubnub.api.builder.SubscribeOperation +import com.pubnub.api.builder.UnsubscribeOperation +import com.pubnub.api.models.SubscriptionItem + +class StateManager { + + /** + * Contains a list of subscribed channels + */ + private val channels: HashMap = hashMapOf() + + /** + * Contains a list of subscribed presence channels. + */ + private val presenceChannels: HashMap = hashMapOf() + + /** + * Contains a list of subscribed channel groups. + */ + private val groups: HashMap = hashMapOf() + + /** + * Contains a list of subscribed presence channel groups. + */ + private val presenceGroups: HashMap = hashMapOf() + + private val heartbeatChannels: HashMap = hashMapOf() + private val heartbeatGroups: HashMap = hashMapOf() + + @Synchronized + internal fun adaptSubscribeBuilder(subscribeOperation: SubscribeOperation) { + for (channel in subscribeOperation.channels) { + if (channel.isEmpty()) { + continue + } + val subscriptionItem = SubscriptionItem(channel) + + channels[channel] = subscriptionItem + + if (subscribeOperation.presenceEnabled) { + val presenceSubscriptionItem = SubscriptionItem(channel) + presenceChannels[channel] = presenceSubscriptionItem + } + } + for (channelGroup in subscribeOperation.channelGroups) { + if (channelGroup.isEmpty()) { + continue + } + val subscriptionItem = SubscriptionItem(channelGroup) + groups.put(channelGroup, subscriptionItem) + if (subscribeOperation.presenceEnabled) { + val presenceSubscriptionItem = SubscriptionItem(channelGroup) + presenceGroups[channelGroup] = presenceSubscriptionItem + } + } + } + + @Synchronized + internal fun adaptStateBuilder(stateOperation: StateOperation) { + for (channel in stateOperation.channels) { + val subscribedChannel = channels[channel] + subscribedChannel?.state = stateOperation.state + } + for (channelGroup in stateOperation.channelGroups) { + val subscribedChannelGroup = groups[channelGroup] + subscribedChannelGroup?.state = stateOperation.state + } + } + + @Synchronized + internal fun adaptUnsubscribeBuilder(unsubscribeOperation: UnsubscribeOperation) { + for (channel in unsubscribeOperation.channels) { + channels.remove(channel) + presenceChannels.remove(channel) + } + for (channelGroup in unsubscribeOperation.channelGroups) { + groups.remove(channelGroup) + presenceGroups.remove(channelGroup) + } + } + + @Synchronized + internal fun adaptPresenceBuilder(presenceOperation: PresenceOperation) { + for (channel in presenceOperation.channels) { + if (channel.isEmpty()) { + continue + } + if (presenceOperation.connected) { + val subscriptionItem = SubscriptionItem(channel) + heartbeatChannels[channel] = subscriptionItem + } else { + heartbeatChannels.remove(channel) + } + } + for (channelGroup in presenceOperation.channelGroups) { + if (channelGroup.isEmpty()) { + continue + } + if (presenceOperation.connected) { + val subscriptionItem = SubscriptionItem(channelGroup) + heartbeatGroups[channelGroup] = subscriptionItem + } else { + heartbeatGroups.remove(channelGroup) + } + } + } + + @Synchronized + fun createStatePayload(): Map { + val stateResponse: HashMap = hashMapOf() + for (channel in channels.values) { + if (channel.state != null) { + stateResponse[channel.name] = channel.state + } + } + for (channelGroup in groups.values) { + if (channelGroup.state != null) { + stateResponse[channelGroup.name] = channelGroup.state + } + } + return stateResponse + } + + @Synchronized + fun prepareChannelList(includePresence: Boolean): List { + return prepareMembershipList(channels, presenceChannels, includePresence) + } + + @Synchronized + fun prepareChannelGroupList(includePresence: Boolean): List { + return prepareMembershipList(groups, presenceGroups, includePresence) + } + + @Synchronized + fun prepareHeartbeatChannelList(includePresence: Boolean): List { + return prepareMembershipList(heartbeatChannels, presenceChannels, includePresence) + } + + @Synchronized + fun prepareHeartbeatChannelGroupList(includePresence: Boolean): List { + return prepareMembershipList(heartbeatGroups, presenceGroups, includePresence) + } + + @Synchronized + fun isEmpty(): Boolean { + return channels.isEmpty() && presenceChannels.isEmpty() && groups.isEmpty() && presenceGroups.isEmpty() + } + + @Synchronized + private fun prepareMembershipList( + dataStorage: Map, + presenceStorage: Map, + includePresence: Boolean + ): List { + val response: MutableList = ArrayList() + for (channelGroupItem in dataStorage.values) { + response.add(channelGroupItem.name) + } + if (includePresence) { + for (presenceChannelGroupItem in presenceStorage.values) { + response.add(presenceChannelGroupItem.name + "-pnpres") + } + } + return response + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt new file mode 100644 index 000000000..a7f86491c --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt @@ -0,0 +1,397 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PubNub +import com.pubnub.api.builder.PresenceOperation +import com.pubnub.api.builder.StateOperation +import com.pubnub.api.builder.SubscribeOperation +import com.pubnub.api.builder.UnsubscribeOperation +import com.pubnub.api.callbacks.ReconnectionCallback +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.endpoints.presence.Heartbeat +import com.pubnub.api.endpoints.presence.Leave +import com.pubnub.api.endpoints.pubsub.Subscribe +import com.pubnub.api.enums.PNHeartbeatNotificationOptions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.server.SubscribeMessage +import com.pubnub.api.workers.SubscribeMessageWorker +import java.util.ArrayList +import java.util.Timer +import java.util.concurrent.LinkedBlockingQueue +import kotlin.concurrent.timerTask + +class SubscriptionManager(val pubnub: PubNub) { + + private companion object { + private const val HEARTBEAT_INTERVAL_MULTIPLIER = 1000L + } + + private var subscribeCall: Subscribe? = null + private var heartbeatCall: Heartbeat? = null + + private var messageQueue: LinkedBlockingQueue = LinkedBlockingQueue() + + private var duplicationManager: DuplicationManager = DuplicationManager(pubnub.configuration) + + /** + * Store the latest timetoken to subscribe with, null by default to get the latest timetoken. + */ + private var timetoken = 0L + private var storedTimetoken: Long? = null // when changing the channel mix, store the timetoken for a later date. + + /** + * Keep track of Region to support PSV2 specification. + */ + private var region: String? = null + + /** + * Timer for heartbeat operations. + */ + private var heartbeatTimer: Timer? = null + + private val subscriptionState = StateManager() + internal val listenerManager = ListenerManager(pubnub) + private val reconnectionManager = ReconnectionManager(pubnub) + + private var consumerThread: Thread? = null + + /** + * lever to indicate if an announcement to the user about the subscription should be made. + * the announcement happens only after the channel mix has been changed. + */ + private var subscriptionStatusAnnounced: Boolean = false + + init { + + reconnectionManager.reconnectionCallback = object : ReconnectionCallback() { + override fun onReconnection() { + reconnect() + listenerManager.announce( + PNStatus( + category = PNStatusCategory.PNReconnectedCategory, + operation = PNOperationType.PNSubscribeOperation, + error = false, + affectedChannels = subscriptionState.prepareChannelList(true), + affectedChannelGroups = subscriptionState.prepareChannelGroupList(true) + ) + ) + subscriptionStatusAnnounced = true + } + + override fun onMaxReconnectionExhaustion() { + listenerManager.announce( + PNStatus( + category = PNStatusCategory.PNReconnectionAttemptsExhausted, + operation = PNOperationType.PNSubscribeOperation, + error = false, + affectedChannels = subscriptionState.prepareChannelList(true), + affectedChannelGroups = subscriptionState.prepareChannelGroupList(true) + ) + ) + + disconnect() + } + } + + if (pubnub.configuration.startSubscriberThread) { + consumerThread = Thread( + SubscribeMessageWorker( + pubnub, + listenerManager, + messageQueue, + duplicationManager + ) + ) + consumerThread?.name = "Subscription Manager Consumer Thread" + consumerThread?.start() + } + } + + @Synchronized + fun getSubscribedChannels(): List? { + return subscriptionState.prepareChannelList(false) + } + + @Synchronized + fun getSubscribedChannelGroups(): List? { + return subscriptionState.prepareChannelGroupList(false) + } + + @Synchronized + internal fun adaptStateBuilder(stateOperation: StateOperation?) { + subscriptionState.adaptStateBuilder(stateOperation!!) + reconnect() + } + + @Synchronized + internal fun adaptSubscribeBuilder(subscribeOperation: SubscribeOperation) { + subscriptionState.adaptSubscribeBuilder(subscribeOperation) + + // the channel mix changed, on the successful subscribe, there is going to be announcement. + subscriptionStatusAnnounced = false + duplicationManager.clearHistory() + + timetoken = subscribeOperation.timetoken + + // if the timetoken is not at starting position, reset the timetoken to get a connected event + // and store the old timetoken to be reused later during subscribe. + if (timetoken != 0L) { + storedTimetoken = timetoken + } + timetoken = 0L + reconnect() + } + + @Synchronized + fun reconnect() { + startSubscribeLoop() + registerHeartbeatTimer() + } + + @Synchronized + fun disconnect() { + heartbeatTimer?.cancel() + stopSubscribeLoop() + } + + private fun registerHeartbeatTimer() { + // make sure only one timer is running at a time. + heartbeatTimer?.cancel() + + // if the interval is 0 or less, do not start the timer + if (pubnub.configuration.heartbeatInterval <= 0) { + return + } + + heartbeatTimer = Timer() + heartbeatTimer?.schedule( + timerTask { + performHeartbeatLoop() + }, + 0, + pubnub.configuration.heartbeatInterval * HEARTBEAT_INTERVAL_MULTIPLIER + ) + } + + private fun performHeartbeatLoop() { + heartbeatCall?.silentCancel() + val presenceChannels = subscriptionState.prepareChannelList(false) + val presenceChannelGroups = + subscriptionState.prepareChannelGroupList(false) + val heartbeatChannels = + subscriptionState.prepareHeartbeatChannelList(false) + val heartbeatChannelGroups = + subscriptionState.prepareHeartbeatChannelGroupList(false) + // do not start the loop if we do not have any presence channels or channel groups enabled. + if (presenceChannels.isEmpty() + && presenceChannelGroups.isEmpty() + && heartbeatChannels.isEmpty() + && heartbeatChannelGroups.isEmpty() + ) { + return + } + val channels: MutableList = ArrayList() + channels.addAll(presenceChannels) + channels.addAll(heartbeatChannels) + val groups: MutableList = ArrayList() + groups.addAll(presenceChannelGroups) + groups.addAll(heartbeatChannelGroups) + heartbeatCall = Heartbeat(pubnub, channels, groups) + heartbeatCall?.async { _, status -> + val heartbeatVerbosity: PNHeartbeatNotificationOptions = + pubnub.configuration.heartbeatNotificationOptions + if (status.error) { + if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL + || heartbeatVerbosity == PNHeartbeatNotificationOptions.FAILURES + ) { + listenerManager.announce(status) + } + // stop the heartbeating logic since an error happened. + heartbeatTimer?.cancel() + } else { + if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL) { + listenerManager.announce(status) + } + } + } + } + + private fun startSubscribeLoop() { + // this function can be called from different points, make sure any old loop is closed + stopSubscribeLoop() + + val combinedChannels = subscriptionState.prepareChannelList(true) + val combinedChannelGroups = subscriptionState.prepareChannelGroupList(true) + val stateStorage = subscriptionState.createStatePayload() + + // do not start the subscribe loop if we have no channels to subscribe to. + if (combinedChannels.isEmpty() && combinedChannelGroups.isEmpty()) { + return + } + + subscribeCall = Subscribe(pubnub).apply { + channels = combinedChannels + channelGroups = combinedChannelGroups + timetoken = this@SubscriptionManager.timetoken + region = this@SubscriptionManager.region + pubnub.configuration.isFilterExpressionKeyValid { + filterExpression = this + } + state = stateStorage + } + + subscribeCall?.async { result, status -> + if (status.error) { + if (status.category == PNStatusCategory.PNTimeoutCategory) { + startSubscribeLoop() + return@async + } + + disconnect() + listenerManager.announce(status) + + if (status.category == PNStatusCategory.PNUnexpectedDisconnectCategory) { + // stop all announcements and ask the reconnection manager to start polling for connection restoration.. + reconnectionManager.startPolling(pubnub.configuration) + } + + return@async + } + + if (!subscriptionStatusAnnounced) { + val pnStatus = createPublicStatus(status).apply { + category = PNStatusCategory.PNConnectedCategory + error = false + } + subscriptionStatusAnnounced = true + listenerManager.announce(pnStatus) + } + + pubnub.configuration.requestMessageCountThreshold?.let { + // todo default value of size if all ?s are null + if (it <= result!!.messages.size) { + listenerManager.announce( + createPublicStatus(status).apply { + category = PNStatusCategory.PNRequestMessageCountExceededCategory + error = false + } + ) + } + } + + if (result!!.messages.isNotEmpty()) { + messageQueue.addAll(result.messages) + } + + if (storedTimetoken != null) { + timetoken = storedTimetoken!! + storedTimetoken = null + } else { + timetoken = result.metadata.timetoken + } + + region = result.metadata.region + startSubscribeLoop() + + } + + } + + private fun stopSubscribeLoop() { + subscribeCall?.silentCancel() + } + + private fun stopHeartbeatLoop() { + heartbeatCall?.silentCancel() + } + + internal fun adaptPresenceBuilder(presenceOperation: PresenceOperation) { + subscriptionState.adaptPresenceBuilder(presenceOperation) + + if (!pubnub.configuration.suppressLeaveEvents && !presenceOperation.connected) { + Leave(pubnub).apply { + channels = presenceOperation.channels + channelGroups = presenceOperation.channelGroups + }.async { _, status -> + listenerManager.announce(status) + } + } + + registerHeartbeatTimer() + } + + @Synchronized + internal fun adaptUnsubscribeBuilder(unsubscribeOperation: UnsubscribeOperation) { + subscriptionState.adaptUnsubscribeBuilder(unsubscribeOperation) + subscriptionStatusAnnounced = false + + if (!pubnub.configuration.suppressLeaveEvents) { + Leave(pubnub).apply { + channels = unsubscribeOperation.channels + channelGroups = unsubscribeOperation.channelGroups + }.async { _, status -> + listenerManager.announce(status) + } + } + + // if we unsubscribed from all the channels, reset the timetoken back to zero and remove the region. + if (subscriptionState.isEmpty()) { + region = null + storedTimetoken = null + timetoken = 0L + } else { + storedTimetoken = timetoken + timetoken = 0L + } + + reconnect() + } + + @Synchronized + fun unsubscribeAll() { + adaptUnsubscribeBuilder( + UnsubscribeOperation().apply { + channels = subscriptionState.prepareChannelList(false) + channelGroups = subscriptionState.prepareChannelGroupList(false) + } + ) + } + + private fun createPublicStatus(privateStatus: PNStatus): PNStatus { + with(privateStatus) { + return PNStatus( + category = category, + error = this.error, + operation = operation, + exception = exception, + statusCode = statusCode, + tlsEnabled = tlsEnabled, + origin = origin, + uuid = uuid, + authKey = authKey, + affectedChannels = affectedChannels, + affectedChannelGroups = affectedChannelGroups + ) + } + } + + fun addListener(listener: SubscribeCallback) { + listenerManager.addListener(listener) + } + + fun removeListener(listener: SubscribeCallback) { + listenerManager.removeListener(listener) + } + + @Synchronized + fun destroy(forceDestroy: Boolean = false) { + synchronized(this) { + disconnect() + if (forceDestroy && consumerThread != null) { + consumerThread!!.interrupt() + } + } + } + +} \ No newline at end of file From 68763b15f26101be7457a22046172419d224d728 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:17:07 +0100 Subject: [PATCH 05/98] Make operations print-friendly --- src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt b/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt index 292d5ef5a..6b7b7fadf 100644 --- a/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt +++ b/src/main/kotlin/com/pubnub/api/enums/PNOperationType.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.enums; +package com.pubnub.api.enums sealed class PNOperationType(open val queryParam: String? = null) { @@ -71,5 +71,8 @@ sealed class PNOperationType(open val queryParam: String? = null) { object PNTimeOperation : TimeOperation() + override fun toString(): String { + return this.javaClass.simpleName + } } \ No newline at end of file From f1113f43d4949f88d27a15121362a0935fe538b2 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:18:11 +0100 Subject: [PATCH 06/98] Implement presence endpoints --- .../pubnub/api/endpoints/presence/GetState.kt | 74 ++++++++++ .../api/endpoints/presence/Heartbeat.kt | 61 ++++++++ .../pubnub/api/endpoints/presence/HereNow.kt | 139 ++++++++++++++++++ .../pubnub/api/endpoints/presence/Leave.kt | 49 ++++++ .../pubnub/api/endpoints/presence/SetState.kt | 84 +++++++++++ .../pubnub/api/endpoints/presence/WhereNow.kt | 39 +++++ 6 files changed, 446 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt new file mode 100644 index 000000000..35da8629a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt @@ -0,0 +1,74 @@ +package com.pubnub.api.endpoints.presence + +import com.google.gson.JsonElement +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNGetStateResult +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.toCsv +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +class GetState(pubnub: PubNub) : Endpoint, PNGetStateResult>(pubnub) { + + var channels = listOf() + var channelGroups = listOf() + var uuid = pubnub.configuration.uuid + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = channelGroups + + override fun validateParams() { + super.validateParams() + if (channels.isNullOrEmpty() && channelGroups.isNullOrEmpty()) { + throw PubNubException(PubNubError.CHANNEL_AND_GROUP_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (channelGroups.isNotEmpty()) { + queryParams["channel-group"] = channelGroups.toCsv() + } + + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.presenceService.getState( + pubnub.configuration.subscribeKey, + channels.toCsv(), + uuid, + queryParams + ) + } + + override fun createResponse(input: Response>): PNGetStateResult? { + val stateMappings = hashMapOf() + if (channels.size == 1 && channelGroups.isEmpty()) { + stateMappings[channels.first()] = input.body()?.payload!! + } else { + val it = pubnub.mapper.getObjectIterator(input.body()?.payload!!) + while (it.hasNext()) { + val stateMapping = it.next() + stateMappings[stateMapping.key] = stateMapping.value + } + } + + return PNGetStateResult( + stateByUUID = stateMappings + ) + } + + + override fun operationType() = PNOperationType.PNGetState + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} + + + diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt new file mode 100644 index 000000000..cad652f32 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt @@ -0,0 +1,61 @@ +package com.pubnub.api.endpoints.presence + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +internal class Heartbeat( + pubnub: PubNub, + val channels: List = listOf(), + val channelGroups: List = listOf() +) : Endpoint, Boolean>(pubnub) { + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = channelGroups + + override fun validateParams() { + super.validateParams() + if (channels.isEmpty() && channelGroups.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_AND_GROUP_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + queryParams["heartbeat"] = pubnub.configuration.presenceTimeout.toString() + + if (channelGroups.isNotEmpty()) { + queryParams["channel-group"] = channelGroups.joinToString(",") + } + + val channelsCsv = + if (channels.isNotEmpty()) + channels.joinToString(",") + else + "," + + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.presenceService.heartbeat( + pubnub.configuration.subscribeKey, + channelsCsv, + queryParams + ) + } + + override fun createResponse(input: Response>) = true + + override fun operationType() = PNOperationType.PNHeartbeatOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt new file mode 100644 index 000000000..2d6fcf88e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt @@ -0,0 +1,139 @@ +package com.pubnub.api.endpoints.presence + +import com.google.gson.JsonElement +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNHereNowChannelData +import com.pubnub.api.models.consumer.presence.PNHereNowOccupantData +import com.pubnub.api.models.consumer.presence.PNHereNowResult +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.toCsv +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +class HereNow(pubnub: PubNub) : Endpoint, PNHereNowResult>(pubnub) { + + var channels = listOf() + var channelGroups = listOf() + var includeState = false + var includeUUIDs = true + + private fun isGlobalHereNow() = channels.isEmpty() && channelGroups.isEmpty() + + // todo try here now without sub key + + override fun getAffectedChannels(): List = channels + + override fun getAffectedChannelGroups(): List = channelGroups + + override fun doWork(queryParams: HashMap): Call> { + if (includeState) { + queryParams["state"] = "1" + } + if (!includeUUIDs) { + queryParams["disable_uuids"] = "1" + } + if (channelGroups.isNotEmpty()) { + queryParams["channel-group"] = channelGroups.toCsv() + } + + return if (!isGlobalHereNow()) { + pubnub.retrofitManager.presenceService.hereNow( + pubnub.configuration.subscribeKey, + channels.toCsv(), + queryParams + ) + } else { + pubnub.retrofitManager.presenceService.globalHereNow( + pubnub.configuration.subscribeKey, + queryParams + ) + } + } + + override fun createResponse(input: Response>): PNHereNowResult? { + return if (isGlobalHereNow()) { + parseMultipleChannelResponse(input.body()?.payload!!) + } else { + if (channels.size > 1 || channelGroups.isNotEmpty()) { + parseMultipleChannelResponse(input.body()?.payload!!) + } else { + parseSingleChannelResponse(input.body()!!) + } + } + } + + private fun parseSingleChannelResponse(input: Envelope): PNHereNowResult { + val pnHereNowResult = PNHereNowResult( + totalChannels = 1, + totalOccupancy = input.occupancy + ) + + val pnHereNowChannelData = PNHereNowChannelData( + channelName = channels[0], + occupancy = input.occupancy + ) + + if (includeUUIDs) { + pnHereNowChannelData.occupants = prepareOccupantData(input.uuids!!) + pnHereNowResult.channels[channels[0]] = pnHereNowChannelData + } + + return pnHereNowResult + } + + private fun parseMultipleChannelResponse(input: JsonElement): PNHereNowResult { + val pnHereNowResult = PNHereNowResult( + totalChannels = pubnub.mapper.elementToInt(input, "total_channels"), + totalOccupancy = pubnub.mapper.elementToInt(input, "total_occupancy") + ) + + val it = pubnub.mapper.getObjectIterator(input, "channels") + + while (it.hasNext()) { + val entry = it.next() + val pnHereNowChannelData = PNHereNowChannelData( + channelName = entry.key, + occupancy = pubnub.mapper.elementToInt(entry.value, "occupancy") + ) + if (includeUUIDs) { + pnHereNowChannelData.occupants = prepareOccupantData(pubnub.mapper.getField(entry.value, "uuids")!!) + } + pnHereNowResult.channels[entry.key] = pnHereNowChannelData + } + + return pnHereNowResult + } + + private fun prepareOccupantData(input: JsonElement): MutableList { + val occupantsResults = mutableListOf() + + val it = pubnub.mapper.getArrayIterator(input) + while (it?.hasNext()!!) { + val occupant = it.next() + occupantsResults.add( + if (includeState) { + PNHereNowOccupantData( + uuid = pubnub.mapper.elementToString(occupant, "uuid")!!, + state = pubnub.mapper.getField(occupant, "state") + ) + } else { + PNHereNowOccupantData( + uuid = pubnub.mapper.elementToString(occupant)!! + ) + } + ) + } + + return occupantsResults + } + + override fun operationType() = PNOperationType.PNHereNowOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt new file mode 100644 index 000000000..7879c0a7b --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.endpoints.presence + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.toCsv +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +internal class Leave(pubnub: PubNub) : Endpoint, Boolean>(pubnub) { + + var channels = emptyList() + var channelGroups = emptyList() + + override fun validateParams() { + super.validateParams() + if (channels.isEmpty() && channelGroups.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_AND_GROUP_MISSING) + } + } + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = channelGroups + + override fun doWork(queryParams: HashMap): Call> { + queryParams["channel-group"] = channelGroups.toCsv() + + return pubnub.retrofitManager.presenceService.leave( + pubnub.configuration.subscribeKey, + channels.toCsv(), + queryParams + ) + } + + override fun createResponse(input: Response>) = true + + override fun operationType() = PNOperationType.PNUnsubscribeOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} + + diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt new file mode 100644 index 000000000..7f01e7f6b --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt @@ -0,0 +1,84 @@ +package com.pubnub.api.endpoints.presence + +import com.google.gson.JsonElement +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.PubNubUtil +import com.pubnub.api.builder.StateOperation +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNSetStateResult +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.throwIfEmpty +import com.pubnub.api.toCsv +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +class SetState(pubnub: PubNub) : + Endpoint, PNSetStateResult>(pubnub) { + + var channels = emptyList() + var channelGroups = emptyList() + var uuid = pubnub.configuration.uuid + lateinit var state: Any + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = channelGroups + + override fun validateParams() { + super.validateParams() + if (channels.isNullOrEmpty() && channelGroups.isNullOrEmpty()) { + throw PubNubException(PubNubError.CHANNEL_AND_GROUP_MISSING) + } + if (!::state.isInitialized) { + throw PubNubException(PubNubError.STATE_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (uuid == pubnub.configuration.uuid) { + pubnub.subscriptionManager.adaptStateBuilder( + StateOperation( + state = state + ).apply { + this.channels = this@SetState.channels + this.channelGroups = this@SetState.channelGroups + } + ) + } + + if (channelGroups.isNotEmpty()) { + queryParams["channel-group"] = channelGroups.toCsv() + } + queryParams["state"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(state)) + + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.presenceService.setState( + pubnub.configuration.subscribeKey, + channels.toCsv(), + uuid, + queryParams + ) + } + + override fun createResponse(input: Response>): PNSetStateResult? { + input.throwIfEmpty() + return PNSetStateResult( + state = input.body()!!.payload + ) + } + + + override fun operationType() = PNOperationType.PNSetStateOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} + + + diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt new file mode 100644 index 000000000..7e464f2c5 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt @@ -0,0 +1,39 @@ +package com.pubnub.api.endpoints.presence + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNWhereNowResult +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.models.server.presence.WhereNowPayload +import com.pubnub.api.throwIfEmpty +import retrofit2.Call +import retrofit2.Response +import java.util.HashMap + +class WhereNow(pubnub: PubNub) : Endpoint, PNWhereNowResult>(pubnub) { + + var uuid = pubnub.configuration.uuid + + override fun getAffectedChannels() = emptyList() + override fun getAffectedChannelGroups() = emptyList() + + override fun doWork(queryParams: HashMap): Call> { + return pubnub.retrofitManager.presenceService.whereNow( + pubnub.configuration.subscribeKey, + uuid, + queryParams + ) + } + + override fun createResponse(input: Response>): PNWhereNowResult? { + input.throwIfEmpty() + return PNWhereNowResult(input.body()!!.payload!!.channels) + } + + override fun operationType() = PNOperationType.PNWhereNowOperation + + override fun isSubKeyRequired() = true + override fun isPubKeyRequired() = false + override fun isAuthRequired() = true +} \ No newline at end of file From eae6dc3590605b2bd6b0c5fa76d3be0f7497f563 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:18:59 +0100 Subject: [PATCH 07/98] Refactor current endpoint implementations --- .../com/pubnub/api/endpoints/History.kt | 86 ++++++++++--------- .../com/pubnub/api/endpoints/Publish.kt | 86 +++++++++---------- .../kotlin/com/pubnub/api/endpoints/Time.kt | 4 - 3 files changed, 85 insertions(+), 91 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/History.kt b/src/main/kotlin/com/pubnub/api/endpoints/History.kt index 07b1a284d..681cad76a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/History.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/History.kt @@ -11,61 +11,56 @@ import com.pubnub.api.models.consumer.history.PNHistoryResult import com.pubnub.api.vendor.Crypto import retrofit2.Call import retrofit2.Response -import java.util.* +import java.util.ArrayList +import java.util.HashMap +import java.util.Locale class History(pubnub: PubNub) : Endpoint(pubnub) { - companion object { + private companion object { private const val MAX_COUNT = 100 } - inner class Params { - var channel: String? = null - var start: Long? = null - var end: Long? = null - var reverse = false - var count: Int? = null - var includeTimetoken = false - var includeMeta = false - } - - val params = Params() + lateinit var channel: String + var start: Long? = null + var end: Long? = null + var count = MAX_COUNT + var reverse = false + var includeTimetoken = false + var includeMeta = false override fun validateParams() { super.validateParams() - if (params.channel.isNullOrBlank()) { + if (!::channel.isInitialized) { throw PubNubException(PubNubError.CHANNEL_MISSING) } + count = + when (count) { + in 1..MAX_COUNT -> count + else -> MAX_COUNT + } } - override fun getAffectedChannels() = listOf(params.channel) + override fun getAffectedChannels() = listOf(channel) override fun getAffectedChannelGroups() = emptyList() override fun doWork(queryParams: HashMap): Call { - queryParams["reverse"] = params.reverse.toString() - queryParams["include_token"] = params.includeTimetoken.toString() - queryParams["include_meta"] = params.includeMeta.toString() - - params.count?.let { - queryParams["count"] = - when (it) { - in 1..MAX_COUNT -> it - else -> MAX_COUNT - }.toString() - } + queryParams["reverse"] = reverse.toString() + queryParams["include_token"] = includeTimetoken.toString() + queryParams["include_meta"] = includeMeta.toString() + queryParams["count"] = count.toString() - params.start?.let { - queryParams["start"] = it.toString().toLowerCase() + start?.let { + queryParams["start"] = it.toString().toLowerCase(Locale.US) } - - params.end?.let { - queryParams["end"] = it.toString().toLowerCase() + end?.let { + queryParams["end"] = it.toString().toLowerCase(Locale.US) } return pubnub.retrofitManager.historyService.fetchHistory( - pubnub.config.subscribeKey!!, - params.channel!!, + pubnub.configuration.subscribeKey, + channel, queryParams ) } @@ -83,15 +78,15 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { if (this.isJsonArray) { pubnub.mapper.getArrayIterator(this)?.forEach { - var message: JsonElement? = null + var message: JsonElement? var timetoken: Long? = null var meta: JsonElement? = null - if (params.includeTimetoken || params.includeMeta) { - if (params.includeTimetoken) { + if (includeTimetoken || includeMeta) { + if (includeTimetoken) { timetoken = pubnub.mapper.elementToLong(it, "timetoken") } - if (params.includeMeta) { + if (includeMeta) { meta = pubnub.mapper.getField(it, "meta") } message = processMessage(pubnub.mapper.getField(it, "message")!!) @@ -99,12 +94,20 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { message = processMessage(it) } - messages += PNHistoryItemResult(message, timetoken, meta) + messages += PNHistoryItemResult( + message, + timetoken, + meta + ) } } } - result = PNHistoryResult(messages, startTimeToken, endTimeToken) + result = PNHistoryResult( + messages, + startTimeToken, + endTimeToken + ) } return result @@ -112,11 +115,10 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { @Throws(PubNubException::class) private fun processMessage(message: JsonElement): JsonElement { - if (pubnub.config.cipherKey == null) { + if (!pubnub.configuration.isCipherKeyValid()) return message - } - val crypto = Crypto(pubnub.config.cipherKey!!) + val crypto = Crypto(pubnub.configuration.cipherKey) val inputText = pubnub.mapper.getField(message, "pn_other")?.let { pubnub.mapper.elementToString(it) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt b/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt index a66676e37..fc55ecce2 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt @@ -1,103 +1,99 @@ package com.pubnub.api.endpoints -import com.pubnub.api.* +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.PubNubUtil import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.vendor.Crypto import retrofit2.Call import retrofit2.Response -import java.util.* class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { - inner class Params { - var channel: String? = null - var message: Any? = null - var shouldStore: Boolean = false - var usePost: Boolean = false - var meta: Any? = null - var replicate = true - var ttl: Int? = null - } + lateinit var channel: String + lateinit var message: Any + lateinit var meta: Any + var shouldStore = false + var usePost = false + var replicate = true + var ttl: Int? = null - val params = Params() + private fun isChannelValid() = ::channel.isInitialized + private fun isMessageValid() = ::message.isInitialized + private fun isMetaValid() = ::meta.isInitialized override fun validateParams() { super.validateParams() - if (params.channel.isNullOrBlank()) { + if (!isChannelValid()) { throw PubNubException(PubNubError.CHANNEL_MISSING) } - if (params.message == null) { + if (!isMessageValid()) { throw PubNubException(PubNubError.MESSAGE_MISSING) } } - override fun getAffectedChannels() = listOf(params.channel) + override fun getAffectedChannels() = listOf(channel) override fun getAffectedChannelGroups() = emptyList() override fun doWork(queryParams: HashMap): Call> { - var stringifiedMessage = pubnub.mapper.toJson(params.message!!) + var stringifiedMessage = pubnub.mapper.toJson(message) - params.meta?.let { - val stringifiedMeta = pubnub.mapper.toJson(it) - PubNubUtil.urlEncode(stringifiedMeta) - queryParams["meta"] = stringifiedMeta + if (isMetaValid()) { + queryParams["meta"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(meta)) } + if (shouldStore) queryParams["store"] = "1" - if (params.shouldStore) { - queryParams["store"] = "1" - } - - params.ttl?.let { - queryParams["ttl"] = it.toString() - } + ttl?.let { queryParams["ttl"] = it.toString() } - when { - !params.replicate -> queryParams["norep"] = "true" - } + if (!replicate) queryParams["norep"] = "true" queryParams["seqn"] = pubnub.publishSequenceManager.nextSequence().toString() queryParams.putAll(encodeParams(queryParams)) - pubnub.config.cipherKey?.let { - stringifiedMessage = Crypto(it).encrypt(stringifiedMessage).replace("\n", "") + if (pubnub.configuration.isCipherKeyValid()) { + stringifiedMessage = Crypto(pubnub.configuration.cipherKey) + .encrypt(stringifiedMessage) + .replace("\n", "") } - if (params.usePost) { - var payload = params.message + if (usePost) { + var payload = message - pubnub.config.cipherKey?.let { + if (pubnub.configuration.isCipherKeyValid()) { payload = stringifiedMessage } return pubnub.retrofitManager.publishService.publishWithPost( - pubnub.config.publishKey!!, - pubnub.config.subscribeKey!!, - params.channel!!, - payload!!, + pubnub.configuration.publishKey, + pubnub.configuration.subscribeKey, + channel, + payload, queryParams ) } else { - pubnub.config.cipherKey?.let { + // get request + + if (pubnub.configuration.isCipherKeyValid()) { stringifiedMessage = "\"$stringifiedMessage\"" } stringifiedMessage = PubNubUtil.urlEncode(stringifiedMessage) return pubnub.retrofitManager.publishService.publish( - pubnub.config.publishKey!!, - pubnub.config.subscribeKey!!, - params.channel!!, + pubnub.configuration.publishKey, + pubnub.configuration.subscribeKey, + channel, stringifiedMessage, queryParams ) } - - } override fun createResponse(input: Response>): PNPublishResult? { diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Time.kt b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt index 732d6cec6..367927706 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/Time.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt @@ -8,10 +8,6 @@ import retrofit2.Response class Time(pubnub: PubNub) : Endpoint, PNTimeResult>(pubnub) { - inner class Params {} - - val params = Params() - override fun getAffectedChannels() = emptyList() override fun getAffectedChannelGroups() = emptyList() From 6e01383ebf2b50e966b09671a6ff138c973ca977 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:20:45 +0100 Subject: [PATCH 08/98] Expose endpoint implementations --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 70 ++++++++++++++----- .../kotlin/com/pubnub/api/PNConfiguration.kt | 33 ++++++--- src/main/kotlin/com/pubnub/api/PubNub.kt | 52 +++++++++----- src/main/kotlin/com/pubnub/api/PubNubError.kt | 10 +++ 4 files changed, 119 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index caf65be83..d0a331f3d 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -3,8 +3,17 @@ package com.pubnub.api import com.google.gson.JsonElement import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory -import com.pubnub.api.enums.PNStatusCategory.* +import com.pubnub.api.enums.PNStatusCategory.PNAccessDeniedCategory +import com.pubnub.api.enums.PNStatusCategory.PNAcknowledgmentCategory +import com.pubnub.api.enums.PNStatusCategory.PNBadRequestCategory +import com.pubnub.api.enums.PNStatusCategory.PNCancelledCategory +import com.pubnub.api.enums.PNStatusCategory.PNMalformedResponseCategory +import com.pubnub.api.enums.PNStatusCategory.PNNotFoundCategory +import com.pubnub.api.enums.PNStatusCategory.PNTimeoutCategory +import com.pubnub.api.enums.PNStatusCategory.PNUnexpectedDisconnectCategory +import com.pubnub.api.enums.PNStatusCategory.PNUnknownCategory import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -12,11 +21,10 @@ import java.io.IOException import java.net.ConnectException import java.net.SocketTimeoutException import java.net.UnknownHostException -import java.util.* +import java.util.HashMap abstract class Endpoint(internal val pubnub: PubNub) { - companion object { private const val SERVER_RESPONSE_SUCCESS = 200 private const val SERVER_RESPONSE_BAD_REQUEST = 400 @@ -24,7 +32,6 @@ abstract class Endpoint(internal val pubnub: PubNub) { private const val SERVER_RESPONSE_NOT_FOUND = 404 } - // private lateinit var cachedCallback: PNCallback private lateinit var cachedCallback: (result: Output?, status: PNStatus) -> Unit private lateinit var call: Call private var silenceFailures = false @@ -39,7 +46,6 @@ abstract class Endpoint(internal val pubnub: PubNub) { call.execute() } catch (e: IOException) { throw PubNubException(PubNubError.PARSING_ERROR).apply { - errorMessage = e.message affectedCall = call } } @@ -84,6 +90,7 @@ abstract class Endpoint(internal val pubnub: PubNub) { when { response.isSuccessful -> { + // query params storeRequestLatency(response) try { Triple(PNAcknowledgmentCategory, createResponse(response), null) @@ -92,7 +99,11 @@ abstract class Endpoint(internal val pubnub: PubNub) { }.let { callback.invoke( it.second, - createStatusResponse(it.first, response, it.third) + createStatusResponse( + category = it.first, + response = response, + exception = it.third + ) ) } } @@ -124,6 +135,7 @@ abstract class Endpoint(internal val pubnub: PubNub) { } override fun onFailure(call: Call, t: Throwable) { + if (silenceFailures) { return } @@ -164,14 +176,20 @@ abstract class Endpoint(internal val pubnub: PubNub) { } private fun storeRequestLatency(response: Response) { - // todo - println("storeRequestLatency: ${operationType().queryParam}") + pubnub.telemetryManager.storeLatency( + latency = with(response.raw()) { + receivedResponseAtMillis() - sentRequestAtMillis() + }, + type = operationType() + ) } protected fun encodeParams(params: Map): Map { val encodedParams = HashMap(params) if (encodedParams.containsKey("auth")) { - encodedParams["auth"] = encodedParams["auth"]?.let { PubNubUtil.urlEncode(it) } + encodedParams["auth"] = encodedParams["auth"]?.let { + PubNubUtil.urlEncode(it) + } } return encodedParams } @@ -179,18 +197,31 @@ abstract class Endpoint(internal val pubnub: PubNub) { private fun createBaseParams(): HashMap { val map = hashMapOf( "pnsdk" to "PubNub-Kotlin/${pubnub.version}", - "uuid" to pubnub.config.uuid, + "uuid" to pubnub.configuration.uuid, "instanceid" to pubnub.instanceId, "requestid" to pubnub.requestId() ) - if (isAuthRequired()) { - pubnub.config.authKey?.let { - map["auth"] = it - } + + if (isAuthRequired() && pubnub.configuration.isAuthKeyValid()) { + map["auth"] = pubnub.configuration.authKey } + + map.putAll(pubnub.telemetryManager.operationsLatency()) return map } + /** + * cancel the operation but do not alert anybody, useful for restarting the heartbeats and subscribe loops. + */ + fun silentCancel() { + if (::call.isInitialized) { + if (!call.isCanceled) { + silenceFailures = true + call.cancel() + } + } + } + private fun createStatusResponse( category: PNStatusCategory, @@ -251,12 +282,11 @@ abstract class Endpoint(internal val pubnub: PubNub) { protected abstract fun getAffectedChannelGroups(): List - @Throws(PubNubException::class) protected open fun validateParams() { - if (isSubKeyRequired() && pubnub.config.subscribeKey.isNullOrBlank()) { + if (isSubKeyRequired() && !pubnub.configuration.isSubscribeKeyValid()) { throw PubNubException(PubNubError.SUBSCRIBE_KEY_MISSING) } - if (isPubKeyRequired() && pubnub.config.publishKey.isNullOrBlank()) { + if (isPubKeyRequired() && !pubnub.configuration.isPublishKeyValid()) { throw PubNubException(PubNubError.PUBLISH_KEY_MISSING) } } @@ -272,4 +302,10 @@ abstract class Endpoint(internal val pubnub: PubNub) { protected abstract fun isSubKeyRequired(): Boolean protected abstract fun isPubKeyRequired(): Boolean protected abstract fun isAuthRequired(): Boolean +} + +internal fun Response>.throwIfEmpty() { + if (body() == null || body()?.payload == null) { + throw PubNubException(PubNubError.PARSING_ERROR) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt index 4a3f1bdda..8efa346be 100644 --- a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt +++ b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt @@ -9,7 +9,7 @@ import okhttp3.ConnectionSpec import okhttp3.logging.HttpLoggingInterceptor import java.net.Proxy import java.net.ProxySelector -import java.util.* +import java.util.UUID import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509ExtendedTrustManager @@ -17,7 +17,7 @@ import javax.net.ssl.X509ExtendedTrustManager class PNConfiguration { - companion object Constants { + private companion object Constants { private const val DEFAULT_DEDUPE_SIZE = 100 private const val PRESENCE_TIMEOUT = 300 private const val MINIMUM_PRESENCE_TIMEOUT = 20 @@ -27,12 +27,13 @@ class PNConfiguration { private const val DEFAULT_BASE_PATH = "ps.pndsn.com" } - var subscribeKey: String? = null - var publishKey: String? = null - var secretKey: String? = null + lateinit var subscribeKey: String + lateinit var publishKey: String + lateinit var secretKey: String + lateinit var authKey: String + lateinit var cipherKey: String + var uuid: String = "pn-${UUID.randomUUID()}" - var authKey: String? = null - var cipherKey: String? = null var origin = DEFAULT_BASE_PATH var secure = true @@ -55,12 +56,13 @@ class PNConfiguration { var subscribeTimeout = SUBSCRIBE_TIMEOUT var connectTimeout = CONNECT_TIMEOUT - var nonSubscribeRequestTimeout = NON_SUBSCRIBE_REQUEST_TIMEOUT + var nonSubscribeRequestTimeout = + NON_SUBSCRIBE_REQUEST_TIMEOUT var maximumMessagesCacheSize = DEFAULT_DEDUPE_SIZE var suppressLeaveEvents = false var disableTokenManager = false - var filterExpression: String? = null + lateinit var filterExpression: String var includeInstanceIdentifier = false var includeRequestIdentifier = true var maximumReconnectionRetries = -1 @@ -79,4 +81,15 @@ class PNConfiguration { var x509ExtendedTrustManager: X509ExtendedTrustManager? = null var connectionSpec: ConnectionSpec? = null var hostnameVerifier: HostnameVerifier? = null -} \ No newline at end of file + + internal fun isSubscribeKeyValid() = ::subscribeKey.isInitialized && !subscribeKey.isBlank() + internal fun isAuthKeyValid() = ::authKey.isInitialized && !authKey.isBlank() + internal fun isCipherKeyValid() = ::cipherKey.isInitialized && !cipherKey.isBlank() + internal fun isPublishKeyValid() = ::publishKey.isInitialized && !publishKey.isBlank() + internal fun isSecretKeyValid() = ::secretKey.isInitialized && !secretKey.isBlank() + internal fun isFilterExpressionKeyValid(function: String.() -> Unit) { + if (::filterExpression.isInitialized && !filterExpression.isBlank()) { + function.invoke(filterExpression) + } + } +} diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt index 1c7c600c6..4df9bf1cd 100644 --- a/src/main/kotlin/com/pubnub/api/PubNub.kt +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -1,25 +1,38 @@ package com.pubnub.api +import com.pubnub.api.builder.SubscribeBuilder +import com.pubnub.api.builder.UnsubscribeBuilder +import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.endpoints.History import com.pubnub.api.endpoints.Publish import com.pubnub.api.endpoints.Time +import com.pubnub.api.endpoints.presence.GetState +import com.pubnub.api.endpoints.presence.HereNow +import com.pubnub.api.endpoints.presence.SetState +import com.pubnub.api.endpoints.presence.WhereNow import com.pubnub.api.managers.BasePathManager import com.pubnub.api.managers.MapperManager import com.pubnub.api.managers.PublishSequenceManager import com.pubnub.api.managers.RetrofitManager -import java.util.* +import com.pubnub.api.managers.SubscriptionManager +import com.pubnub.api.managers.TelemetryManager +import java.util.Date +import java.util.UUID -class PubNub(val config: PNConfiguration) { +class PubNub(val configuration: PNConfiguration) { - companion object Constants { + private companion object Constants { private const val TIMESTAMP_DIVIDER = 1000 - private const val SDK_VERSION = "4.29.1" + private const val SDK_VERSION = "0.0.1-canary" } - internal val mapper = MapperManager() private val basePathManager = BasePathManager(this) + + internal val mapper = MapperManager() internal val retrofitManager = RetrofitManager(this) internal val publishSequenceManager = PublishSequenceManager() + internal val telemetryManager = TelemetryManager() + internal val subscriptionManager = SubscriptionManager(this) val version = SDK_VERSION val instanceId = UUID.randomUUID().toString() @@ -28,22 +41,23 @@ class PubNub(val config: PNConfiguration) { fun requestId() = UUID.randomUUID().toString() fun timestamp() = Date().time / TIMESTAMP_DIVIDER - inline fun time(function: Time.Params.() -> Unit): Time { - val time = Time(this) - time.params.function() - return time - } - - inline fun publish(function: Publish.Params.() -> Unit): Publish { - val publish = Publish(this) - publish.params.function() - return publish + fun publish() = Publish(this) + fun subscribe() = SubscribeBuilder(subscriptionManager) + fun unsubscribe() = UnsubscribeBuilder(subscriptionManager) + fun history() = History(this) + fun hereNow() = HereNow(this) + fun whereNow() = WhereNow(this) + fun setState() = SetState(this) + fun getState() = GetState(this) + fun time() = Time(this) + + fun addListener(listener: SubscribeCallback) { + subscriptionManager.addListener(listener) } - inline fun history(function: History.Params.() -> Unit): History { - val history = History(this) - history.params.function() - return history + fun destroy() { + subscriptionManager.destroy() + retrofitManager.destroy() } } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNubError.kt b/src/main/kotlin/com/pubnub/api/PubNubError.kt index bcc255894..912bb9fc0 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubError.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubError.kt @@ -60,6 +60,16 @@ enum class PubNubError(val code: Int, val message: String) { CRYPTO_ERROR( 135, "Error while encrypting/decrypting message. Please contact support with error details." + ), + + STATE_MISSING( + 140, + "State Missing." + ), + + CHANNEL_AND_GROUP_MISSING( + 141, + "Channel and Group Missing." ); override fun toString(): String { From e7a7e38c368ae22fa10387a0401f4358a331c49c Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:21:17 +0100 Subject: [PATCH 09/98] Implement the subscribe loop --- .../pubnub/api/services/PresenceService.kt | 63 ++++++ .../pubnub/api/services/SubscribeService.kt | 16 ++ .../api/workers/SubscribeMessageWorker.kt | 205 ++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/services/PresenceService.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/SubscribeService.kt create mode 100644 src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt diff --git a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt new file mode 100644 index 000000000..3f52c4040 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt @@ -0,0 +1,63 @@ +package com.pubnub.api.services + +import com.google.gson.JsonElement +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.models.server.presence.WhereNowPayload +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +interface PresenceService { + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/leave") + fun leave( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap options: Map + ): Call> + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/heartbeat") + fun heartbeat( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap(encoded = true) options: Map + ): Call> + + @GET("v2/presence/sub-key/{subKey}/uuid/{uuid}") + fun whereNow( + @Path("subKey") subKey: String, + @Path("uuid") uuid: String, + @QueryMap options: Map + ): Call> + + @GET("v2/presence/sub_key/{subKey}/channel/{channel}") + fun hereNow( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap options: Map + ): Call> + + @GET("v2/presence/sub_key/{subKey}") + fun globalHereNow( + @Path("subKey") subKey: String, + @QueryMap options: Map + ): Call> + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/uuid/{uuid}") + fun getState( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path("uuid") uuid: String, + @QueryMap options: Map + ): Call> + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/uuid/{uuid}/data") + fun setState( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path("uuid") uuid: String, + @QueryMap(encoded = true) options: Map + ): Call> + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt b/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt new file mode 100644 index 000000000..7eeb0672a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt @@ -0,0 +1,16 @@ +package com.pubnub.api.services + +import com.pubnub.api.models.server.SubscribeEnvelope +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +interface SubscribeService { + @GET("v2/subscribe/{subKey}/{channel}/0") + fun subscribe( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap(encoded = true) options: Map + ): Call +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt new file mode 100644 index 000000000..8e11248fc --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt @@ -0,0 +1,205 @@ +package com.pubnub.api.workers + +import com.google.gson.JsonElement +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubException +import com.pubnub.api.PubNubUtil +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.managers.DuplicationManager +import com.pubnub.api.managers.ListenerManager +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.server.PresenceEnvelope +import com.pubnub.api.models.server.SubscribeMessage +import com.pubnub.api.vendor.Crypto +import java.util.concurrent.LinkedBlockingQueue + +class SubscribeMessageWorker( + val pubnub: PubNub, + val listenerManager: ListenerManager, + val queue: LinkedBlockingQueue, + val duplicationManager: DuplicationManager +) : Runnable { + + private var running = false + + companion object { + private const val TYPE_MESSAGE = 0 + private const val TYPE_SIGNAL = 1 + private const val TYPE_OBJECT = 2 + private const val TYPE_MESSAGE_ACTION = 3 + } + + override fun run() { + takeMessage() + } + + private fun takeMessage() { + running = true + + while (running) { + try { + processIncomingPayload(queue.take()) + } catch (e: InterruptedException) { + running = false + print("take message interrupted!") + } + } + } + + private fun processIncomingPayload(message: SubscribeMessage) { + if (message.channel == null) { + return + } + + val channel = message.channel + var subscriptionMatch = message.subscriptionMatch + val publishMetaData = message.publishMetaData + + if (channel == subscriptionMatch) { + subscriptionMatch = null + } + + if (pubnub.configuration.dedupOnSubscribe) { + if (duplicationManager.isDuplicate(message)) { + return + } else { + duplicationManager.addEntry(message) + } + } + + if (message.channel.endsWith("-pnpres")) { + val presencePayload = pubnub.mapper.convertValue(message.payload, PresenceEnvelope::class.java) + val strippedPresenceChannel = PubNubUtil.replaceLast(channel, "-pnpres", "") + val strippedPresenceSubscription = subscriptionMatch?.let { + PubNubUtil.replaceLast(it, "-pnpres", "") + } + + val isHereNowRefresh = message.payload?.asJsonObject?.get("here_now_refresh") + + listenerManager.announce( + PNPresenceEventResult( + event = presencePayload.action, + uuid = presencePayload.uuid, + timestamp = presencePayload.timestamp, + occupancy = presencePayload.occupancy, + state = presencePayload.data, + channel = strippedPresenceChannel, + subscription = strippedPresenceSubscription, + timetoken = publishMetaData?.publishTimetoken, + join = getDelta(message.payload?.asJsonObject?.get("join")), + leave = getDelta(message.payload?.asJsonObject?.get("leave")), + timeout = getDelta(message.payload?.asJsonObject?.get("timeout")), + hereNowRefresh = isHereNowRefresh != null && isHereNowRefresh.asBoolean + ) + ) + } else { + val extractedMessage = processMessage(message) + + if (extractedMessage == null) { + println("unable to parse payload on #processIncomingMessages") + } + + val result = BasePubSubResult( + channel = channel, + subscription = subscriptionMatch, + timetoken = publishMetaData?.publishTimetoken, + userMetadata = message.userMetadata, + publisher = message.issuingClientId + ) + + when (message.type) { + null -> { + listenerManager.announce(PNMessageResult(result, extractedMessage!!)) + } + TYPE_MESSAGE -> { + listenerManager.announce(PNMessageResult(result, extractedMessage!!)) + } + TYPE_SIGNAL -> { + listenerManager.announce(PNSignalResult(result, extractedMessage!!)) + } + TYPE_OBJECT -> { + + } + } + + + } + + } + + private fun processMessage(subscribeMessage: SubscribeMessage): JsonElement? { + val input = subscribeMessage.payload + + // if we do not have a crypto key, there is no way to process the node; let's return. + if (!pubnub.configuration.isCipherKeyValid()) { + return input + } + + // if the message couldn't possibly be encrypted in the first place, there is no way to process the node; + // let's return. + if (!subscribeMessage.supportsEncryption()) { + return input + } + + val crypto = Crypto(pubnub.configuration.cipherKey) + + var inputText = pubnub.mapper.elementToString(input, "pn_other")?.let { it } + if (inputText == null) { + inputText = pubnub.mapper.elementToString(input) + } + + val outputText = + try { + crypto.decrypt(inputText!!) + } catch (e: PubNubException) { + val pnStatus = PNStatus( + PNStatusCategory.PNMalformedResponseCategory, + true, + PNOperationType.PNSubscribeOperation, + e + ) + listenerManager.announce(pnStatus) + return null + } + + var outputObject = + try { + pubnub.mapper.fromJson(outputText, JsonElement::class.java) + } catch (e: PubNubException) { + val pnStatus = PNStatus( + PNStatusCategory.PNMalformedResponseCategory, + true, + PNOperationType.PNSubscribeOperation, + e + ) + listenerManager.announce(pnStatus) + return null + } + + if (pubnub.mapper.isJsonObject(input!!) && pubnub.mapper.hasField(input, "pn_other")) { + val objectNode = pubnub.mapper.getAsObject(input) + pubnub.mapper.putOnObject(objectNode, "pn_other", outputObject) + outputObject = objectNode + } + + return outputObject + } + + private fun getDelta(delta: JsonElement?): List { + val list = mutableListOf() + delta?.let { + it.asJsonArray.forEach { item: JsonElement? -> + item?.let { + list.add(it.asString) + } + } + } + return list + } + +} \ No newline at end of file From b97d3aef9fec6ed86b92eada71bed461db3fa17c Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:21:51 +0100 Subject: [PATCH 10/98] Add TelemetryManager --- .../pubnub/api/managers/TelemetryManager.kt | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt diff --git a/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt new file mode 100644 index 000000000..3486ec3ea --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt @@ -0,0 +1,121 @@ +package com.pubnub.api.managers + +import com.pubnub.api.enums.PNOperationType +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Date +import java.util.Locale +import java.util.Timer +import java.util.TimerTask + +class TelemetryManager { + + companion object { + private const val MAX_FRACTION_DIGITS = 3 + private const val TIMESTAMP_DIVIDER = 1000 + private const val MAXIMUM_LATENCY_DATA_AGE = 60.0 + private const val CLEAN_UP_INTERVAL = 1 + private const val CLEAN_UP_INTERVAL_MULTIPLIER = 1000 + } + + private var timer: Timer? = Timer() + private val latencies: HashMap>> = HashMap() + private val numberFormat by lazy { + NumberFormat.getNumberInstance(Locale.US).apply { + maximumFractionDigits = + MAX_FRACTION_DIGITS + roundingMode = RoundingMode.HALF_UP + isGroupingUsed = false + } + } + + init { + startCleanUpTimer() + } + + @Synchronized + fun operationsLatency(): Map { + val operationLatencies = HashMap() + latencies.entries.forEach { + val latencyKey = "l_${it.key}" + val endpointAverageLatency = averageLatencyFromData(it.value) + if (endpointAverageLatency > 0.0f) { + operationLatencies[latencyKey] = numberFormat.format(endpointAverageLatency) + } + } + return operationLatencies + } + + private fun startCleanUpTimer() { + val interval = (CLEAN_UP_INTERVAL * CLEAN_UP_INTERVAL_MULTIPLIER).toLong() + + stopCleanUpTimer() + timer = Timer() + timer?.schedule(object : TimerTask() { + override fun run() { + cleanUpTelemetryData() + } + }, interval, interval) + } + + + private fun stopCleanUpTimer() { + this.timer?.cancel() + } + + @Synchronized + private fun cleanUpTelemetryData() { + val currentDate = Date().time / (TIMESTAMP_DIVIDER.toDouble()) + val endpoints = latencies.keys.toList() + endpoints.forEach { + val outdatedLatencies = ArrayList>() + val operationLatencies = ArrayList>() + operationLatencies.forEach { map: Map -> + map["d"]?.let { d: Double -> + if (currentDate - d > MAXIMUM_LATENCY_DATA_AGE) { + outdatedLatencies.add(map) + } + } + } + if (outdatedLatencies.size > 0) { + operationLatencies.removeAll(outdatedLatencies) + } + if (operationLatencies.size == 0) { + this.latencies.remove(it) + } + } + } + + private fun averageLatencyFromData(endpointLatencies: List>): Double { + var totalLatency = 0.0 + endpointLatencies.forEach { + it["l"]?.let { l: Double -> + totalLatency += l + } + } + return totalLatency / endpointLatencies.size + } + + @Synchronized + internal fun storeLatency(latency: Long, type: PNOperationType) { + type.queryParam?.let { queryParam: String -> + if (latency > 0) { + val storeDate = Date().time / (TIMESTAMP_DIVIDER.toDouble()) + + if (latencies[queryParam] == null) { + latencies[queryParam] = ArrayList() + } + + latencies[queryParam]?.let { + latencies[queryParam] = it + + val latencyEntry = java.util.HashMap() + latencyEntry["d"] = storeDate + latencyEntry["l"] = latency.toDouble() / TIMESTAMP_DIVIDER + it.add(latencyEntry) + } + } + } + } + +} \ No newline at end of file From a50e986c893bb83d1d1e18e247a5108c34ee13e3 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:22:19 +0100 Subject: [PATCH 11/98] Add ListenerManager --- .../pubnub/api/managers/ListenerManager.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt diff --git a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt new file mode 100644 index 000000000..9d47814ad --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt @@ -0,0 +1,52 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import java.util.ArrayList + +class ListenerManager(val pubnub: PubNub) { + private val listeners = mutableListOf() + + fun addListener(listener: SubscribeCallback) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun removeListener(listener: SubscribeCallback) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + private fun getListeners(): List { + val tempCallbackList = ArrayList() + synchronized(listeners) { + tempCallbackList.addAll(listeners) + } + return tempCallbackList + } + + @Synchronized + fun announce(status: PNStatus) { + getListeners().forEach { it.status(pubnub, status) } + } + + fun announce(message: PNMessageResult) { + getListeners().forEach { it.message(pubnub, message) } + } + + fun announce(presence: PNPresenceEventResult) { + getListeners().forEach { it.presence(pubnub, presence) } + } + + fun announce(signal: PNSignalResult) { + getListeners().forEach { it.signal(pubnub, signal) } + } + + +} \ No newline at end of file From 25ee177bb3844f0019fd10ec9f6628b91648ba9b Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:22:46 +0100 Subject: [PATCH 12/98] Add DuplicationManager --- .../pubnub/api/managers/DuplicationManager.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt diff --git a/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt b/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt new file mode 100644 index 000000000..287ee41ec --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt @@ -0,0 +1,29 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PNConfiguration +import com.pubnub.api.models.server.SubscribeMessage + +class DuplicationManager( + private val config: PNConfiguration +) { + + private val hashHistory: ArrayList = ArrayList() + + private fun getKey(message: SubscribeMessage) = + with(message) { + "${publishMetaData?.publishTimetoken}-${payload.hashCode()}" + } + + + fun isDuplicate(message: SubscribeMessage) = hashHistory.contains(getKey(message)) + + fun addEntry(message: SubscribeMessage) { + if (hashHistory.size >= config.maximumMessagesCacheSize) { + hashHistory.removeAt(0) + } + hashHistory.add(getKey(message)) + } + + fun clearHistory() = hashHistory.clear() + +} \ No newline at end of file From 74b2b7a208d7ed219448c07f83bb5d8d2ca8e962 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:23:02 +0100 Subject: [PATCH 13/98] Add ReconnectionManager --- .../api/managers/ReconnectionManager.kt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt diff --git a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt new file mode 100644 index 000000000..e53aeae73 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt @@ -0,0 +1,108 @@ +package com.pubnub.api.managers + +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.ReconnectionCallback +import com.pubnub.api.enums.PNReconnectionPolicy +import java.util.Calendar +import java.util.Timer +import java.util.TimerTask +import kotlin.math.pow + +class ReconnectionManager(val pubnub: PubNub) { + + private companion object { + private const val LINEAR_INTERVAL = 3 + private const val MIN_EXPONENTIAL_BACKOFF = 1 + private const val MAX_EXPONENTIAL_BACKOFF = 32 + + private const val MILLISECONDS = 1000 + } + + internal lateinit var reconnectionCallback: ReconnectionCallback + + private var exponentialMultiplier = 1 + private var failedCalls = 0 + + private lateinit var pnReconnectionPolicy: PNReconnectionPolicy + private var maxConnectionRetries = -1 + + private val timer = Timer() + + internal fun startPolling(pnConfiguration: PNConfiguration) { + pnReconnectionPolicy = pnConfiguration.reconnectionPolicy + maxConnectionRetries = pnConfiguration.maximumReconnectionRetries + + if (isReconnectionPolicyUndefined()) + return + + exponentialMultiplier = 1 + failedCalls = 0 + + registerHeartbeatTimer() + } + + private fun registerHeartbeatTimer() { + // make sure only one timer is running at a time. + stopHeartbeatTimer() + + if (isReconnectionPolicyUndefined()) { + return + } + + if (maxConnectionRetries != -1 && failedCalls >= maxConnectionRetries) { + reconnectionCallback.onMaxReconnectionExhaustion() + return + } + timer.schedule(object : TimerTask() { + override fun run() { + callTime() + } + }, getBestInterval() * MILLISECONDS.toLong()) + } + + private fun getBestInterval(): Int { + var timerInterval = LINEAR_INTERVAL + if (pnReconnectionPolicy == PNReconnectionPolicy.EXPONENTIAL) { + timerInterval = (2.0.pow(exponentialMultiplier.toDouble()) - 1).toInt() + if (timerInterval > MAX_EXPONENTIAL_BACKOFF) { + timerInterval = MIN_EXPONENTIAL_BACKOFF + exponentialMultiplier = 1 + println("timerInterval > MAXEXPONENTIALBACKOFF at: " + Calendar.getInstance().time.toString()) + } else if (timerInterval < 1) { + timerInterval = MIN_EXPONENTIAL_BACKOFF + } + println("timerInterval = " + timerInterval + " at: " + Calendar.getInstance().time.toString()) + } + if (pnReconnectionPolicy == PNReconnectionPolicy.LINEAR) { + timerInterval = LINEAR_INTERVAL + } + return timerInterval + } + + private fun stopHeartbeatTimer() { + timer.cancel() + } + + private fun callTime() { + // todo reconnection + pubnub.time() + .async { _, status -> + if (!status.error) { + + } else { + + } + } + } + + + private fun isReconnectionPolicyUndefined(): Boolean { + if (pnReconnectionPolicy == PNReconnectionPolicy.NONE) { + println("reconnection policy is disabled, please handle reconnection manually.") + return true + } + return false + } + +} \ No newline at end of file From 63f95c03cfdf91616a2a3294d016ebc1d08f18d1 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:23:23 +0100 Subject: [PATCH 14/98] Refactor and cleanup --- src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 17 ++++++ .../pubnub/api/managers/BasePathManager.kt | 4 +- .../com/pubnub/api/managers/MapperManager.kt | 23 +++++--- .../pubnub/api/managers/RetrofitManager.kt | 57 +++++++++++++++---- 4 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt index 74fd5e8b2..a34663ae9 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -24,5 +24,22 @@ class PubNubUtil { } + fun replaceLast(string: String, toReplace: String, replacement: String): String { + val pos = string.lastIndexOf(toReplace) + return if (pos > -1) { + string.substring(0, pos) + replacement + string.substring( + pos + toReplace.length, + string.length + ) + } else { + string + } + } } +} + +internal fun List.toCsv(): String { + if (this.isNotEmpty()) + return this.joinToString(",") + return "," } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt index 284db649a..c6d66a195 100644 --- a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt @@ -6,9 +6,9 @@ class BasePathManager(val pubnub: PubNub) { fun basePath(): String { return StringBuilder("http") - .append(if (pubnub.config.secure) "s" else "") + .append(if (pubnub.configuration.secure) "s" else "") .append("://") - .append(pubnub.config.origin) + .append(pubnub.configuration.origin) .toString() } diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt index 546db5d3e..9cc3dafd6 100644 --- a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -1,7 +1,14 @@ package com.pubnub.api.managers -import com.google.gson.* -import com.google.gson.internal.bind.TypeAdapters.* +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.TypeAdapter +import com.google.gson.internal.bind.TypeAdapters.BOOLEAN +import com.google.gson.internal.bind.TypeAdapters.NUMBER +import com.google.gson.internal.bind.TypeAdapters.STRING import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import com.pubnub.api.PubNubError @@ -59,9 +66,9 @@ class MapperManager { fun getObjectIterator(element: JsonElement, field: String) = element.asJsonObject.get(field).asJsonObject.entrySet().iterator() - fun elementToString(element: JsonElement) = element.asString + fun elementToString(element: JsonElement?) = element?.asString - fun elementToString(element: JsonElement, field: String) = element.asJsonObject.get(field).asString + fun elementToString(element: JsonElement?, field: String) = element?.asJsonObject?.get(field)?.asString fun elementToInt(element: JsonElement, field: String) = element.asJsonObject.get(field).asInt @@ -92,14 +99,14 @@ class MapperManager { } } - fun convertValue(input: JsonElement, clazz: Class): T { - return this.objectMapper.fromJson(input, clazz) as T + fun convertValue(input: JsonElement?, clazz: Class): T { + return this.objectMapper.fromJson(input, clazz) as T } - @Throws(PubNubException::class) + /*@Throws(PubNubException::class) fun convertValue(obj: Any, clazz: Class): T { return fromJson(toJson(obj), clazz) - } + }*/ @Throws(PubNubException::class) fun toJson(input: Any): String { diff --git a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt index a95b7f898..33ca2d42d 100644 --- a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt @@ -3,7 +3,9 @@ package com.pubnub.api.managers import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity import com.pubnub.api.services.HistoryService +import com.pubnub.api.services.PresenceService import com.pubnub.api.services.PublishService +import com.pubnub.api.services.SubscribeService import com.pubnub.api.services.TimeService import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -14,19 +16,40 @@ import java.util.concurrent.TimeUnit class RetrofitManager(val pubnub: PubNub) { - private val transactionClientInstance = createOkHttpClient(pubnub.config.nonSubscribeRequestTimeout) - private val transactionInstance = createRetrofit(transactionClientInstance) + private val transactionClientInstance: OkHttpClient by lazy { + createOkHttpClient(pubnub.configuration.nonSubscribeRequestTimeout) + } + + private val subscriptionClientInstance: OkHttpClient by lazy { + createOkHttpClient(pubnub.configuration.subscribeTimeout) + } + + internal val timeService: TimeService + internal val publishService: PublishService + internal val historyService: HistoryService + internal val presenceService: PresenceService + + internal val subscribeService: SubscribeService + + + init { + val transactionInstance = createRetrofit(transactionClientInstance) + val subscriptionInstance = createRetrofit(subscriptionClientInstance) - val timeService: TimeService = transactionInstance.create(TimeService::class.java) - val publishService: PublishService = transactionInstance.create(PublishService::class.java) - val historyService: HistoryService = transactionInstance.create(HistoryService::class.java) + timeService = transactionInstance.create(TimeService::class.java) + publishService = transactionInstance.create(PublishService::class.java) + historyService = transactionInstance.create(HistoryService::class.java) + presenceService = transactionInstance.create(PresenceService::class.java) + + subscribeService = subscriptionInstance.create(SubscribeService::class.java) + } private fun createOkHttpClient(readTimeout: Int): OkHttpClient { val okHttpBuilder = OkHttpClient.Builder() .readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) - .connectTimeout(pubnub.config.connectTimeout.toLong(), TimeUnit.SECONDS) + .connectTimeout(pubnub.configuration.connectTimeout.toLong(), TimeUnit.SECONDS) - with(pubnub.config) { + with(pubnub.configuration) { if (logVerbosity == PNLogVerbosity.BODY) { okHttpBuilder.addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY @@ -34,8 +57,8 @@ class RetrofitManager(val pubnub: PubNub) { } if (sslSocketFactory != null && x509ExtendedTrustManager != null) { okHttpBuilder.sslSocketFactory( - pubnub.config.sslSocketFactory!!, - pubnub.config.x509ExtendedTrustManager!! + pubnub.configuration.sslSocketFactory!!, + pubnub.configuration.x509ExtendedTrustManager!! ) } connectionSpec?.let { okHttpBuilder.connectionSpecs(listOf(it)) } @@ -48,7 +71,7 @@ class RetrofitManager(val pubnub: PubNub) { val okHttpClient = okHttpBuilder.build() - pubnub.config.maximumConnections?.let { okHttpClient.dispatcher().maxRequestsPerHost = it } + pubnub.configuration.maximumConnections?.let { okHttpClient.dispatcher().maxRequestsPerHost = it } return okHttpClient } @@ -61,4 +84,18 @@ class RetrofitManager(val pubnub: PubNub) { return retrofitBuilder.build() } + + fun destroy(force: Boolean = false) { + closeExecutor(transactionClientInstance, force) + closeExecutor(subscriptionClientInstance, force) + } + + private fun closeExecutor(client: OkHttpClient, force: Boolean) { + client.dispatcher().cancelAll() + if (force) { + client.connectionPool().evictAll() + val executorService = client.dispatcher().executorService() + executorService.shutdown() + } + } } \ No newline at end of file From 53fdc46345ee82620f5dd1e6ad4d9c04c2ce1ed5 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:25:12 +0100 Subject: [PATCH 15/98] Add e2e tests --- .gitignore | 3 +- build.gradle | 7 + src/test/kotlin/com/pubnub/api/AppTest.kt | 208 ++++++++++++++++++++ src/test/kotlin/com/pubnub/api/BaseTest.kt | 69 +++++++ src/test/kotlin/com/pubnub/tests/AppTest.kt | 69 ------- 5 files changed, 286 insertions(+), 70 deletions(-) create mode 100644 src/test/kotlin/com/pubnub/api/AppTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/BaseTest.kt delete mode 100644 src/test/kotlin/com/pubnub/tests/AppTest.kt diff --git a/.gitignore b/.gitignore index c566e2044..c5298fd18 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ build/ *.iws *.class bin/ -gen/ \ No newline at end of file +gen/ +/src/test/resources/config.properties \ No newline at end of file diff --git a/build.gradle b/build.gradle index 398f1e605..df3e34d0d 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,11 @@ version '0.0.1' repositories { mavenCentral() + jcenter() +} + +configurations { + ktlint } dependencies { @@ -29,6 +34,8 @@ dependencies { // implementation group: 'log4j', name: 'log4j', version: '1.2.17' testImplementation 'org.awaitility:awaitility-kotlin:4.0.1' + + implementation 'com.github.tomakehurst:wiremock:2.26.3' } test { diff --git a/src/test/kotlin/com/pubnub/api/AppTest.kt b/src/test/kotlin/com/pubnub/api/AppTest.kt new file mode 100644 index 000000000..b7c57858f --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/AppTest.kt @@ -0,0 +1,208 @@ +package com.pubnub.api + +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNOperationType.PNTimeOperation +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.enums.PNStatusCategory.PNUnknownCategory +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult +import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult +import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle +import java.text.SimpleDateFormat +import java.util.Properties +import java.util.UUID +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean + +@TestInstance(Lifecycle.PER_CLASS) +class AppTest { + + lateinit var pubnub: PubNub + lateinit var pubKey: String + lateinit var subKey: String + + @BeforeAll + fun extractProperties() { + val inputStream = javaClass.classLoader.getResourceAsStream("config.properties") + val properties = Properties() + properties.load(inputStream) + pubKey = properties.getProperty("pub_key") + subKey = properties.getProperty("sub_key") + } + + @BeforeEach + fun initPubnub() { + pubnub = PubNub( + PNConfiguration().apply { + subscribeKey = subKey + publishKey = pubKey + logVerbosity = PNLogVerbosity.BODY + } + ) + } + + @Test + fun testPublishSync() { + pubnub.publish().apply { + channel = UUID.randomUUID().toString() + message = UUID.randomUUID().toString() + }.sync().let { + Assertions.assertNotNull(it) + } + } + + @Test + fun testPublishAsync() { + val success = AtomicBoolean() + + pubnub.publish().apply { + channel = UUID.randomUUID().toString() + message = UUID.randomUUID().toString() + }.async { result, status -> + assertFalse(status.error) + result!!.timetoken + success.set(true) + } + + success.listen() + + success.set(false) + + Thread.sleep(2000) + + pubnub.publish().apply { + channel = UUID.randomUUID().toString() + message = UUID.randomUUID().toString() + }.async { result, status -> + status.printQueryParams() + assertFalse(status.error) + result!!.timetoken + success.set(true) + } + + success.listen() + } + + @Test + fun testSubscribe() { + val success = AtomicBoolean() + val expectedChannel = UUID.randomUUID().toString() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + assertTrue(pnStatus.operation == PNOperationType.PNSubscribeOperation) + assertTrue(pnStatus.category == PNStatusCategory.PNConnectedCategory) + assertTrue(pnStatus.affectedChannels.contains(expectedChannel)) + success.set(true) + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + val subscribe = pubnub.subscribe() + subscribe.channels = listOf(expectedChannel) + subscribe.withPresence = true + + subscribe.execute() + + success.listen() + } + + @Test + fun testSynchronizedAccess() { + val size = 60 + var counter = 0 + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + counter++ + val time = SimpleDateFormat("HH:mm:ss:SSS").format(System.currentTimeMillis()) + println("$time ${pnStatus.authKey} [$counter]") + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + val pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) + + repeat(size) { + pool.execute { + pubnub.subscriptionManager.listenerManager.announce( + PNStatus( + category = PNUnknownCategory, + error = false, + operation = PNTimeOperation + ).apply { + authKey = Thread.currentThread().name + } + ) + } + } + + Awaitility.await() + .atMost(Durations.TEN_SECONDS) + .conditionEvaluationListener { + if (it.remainingTimeInMS < 300) { + println("Almost done. Counter value: $counter") + } + } + .until { counter == size } + } + + @Test + fun testHereNow() { + val expectedChannels = listOf(UUID.randomUUID().toString()) + + pubnub.subscribe().apply { + channels = expectedChannels + withPresence = true + }.execute() + + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .pollDelay(Durations.ONE_SECOND) + .pollInterval(Durations.ONE_SECOND) + .with() + .until { + pubnub.whereNow().apply { + uuid = pubnub.configuration.uuid + }.sync()!! + .channels + .containsAll(expectedChannels) + } + + val pnHereNowResult = pubnub.hereNow().apply { + channels = expectedChannels + includeUUIDs = false + includeState = false + }.sync() + } +} diff --git a/src/test/kotlin/com/pubnub/api/BaseTest.kt b/src/test/kotlin/com/pubnub/api/BaseTest.kt new file mode 100644 index 000000000..8428e9816 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/BaseTest.kt @@ -0,0 +1,69 @@ +package com.pubnub.api + +import com.github.tomakehurst.wiremock.WireMockServer +import com.pubnub.api.models.consumer.PNStatus +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import java.util.concurrent.atomic.AtomicBoolean + +abstract class BaseTest { + + private val wireMockServer = WireMockServer() + + protected val pubnub = PubNub(PNConfiguration().apply { + subscribeKey = "mySubscribeKey" + publishKey = "myPublishKey" + uuid = "myUUID" + origin = "localhost:8080" + secure = false + }) + + @BeforeEach + fun beforeEach() { + wireMockServer.start() + } + + @AfterEach + fun afterEach() { + wireMockServer.stop() + } + +} + +private fun observe(success: AtomicBoolean) { + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .until(success::get) +} + +fun AtomicBoolean.listen(): AtomicBoolean { + observe(this) + return this +} + +fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .until { + function.invoke() + } + return this +} + +fun PNStatus.printQueryParams() { + val map = this.clientRequest?.url()?.queryParameterNames() + ?.map { + print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") + } +} + +fun assertPnException(pnStatus: PNStatus, pubNubError: PubNubError) { + Assertions.assertTrue(pnStatus.error) + Assertions.assertEquals(pubNubError, pnStatus.exception!!.pubnubError) +} + diff --git a/src/test/kotlin/com/pubnub/tests/AppTest.kt b/src/test/kotlin/com/pubnub/tests/AppTest.kt deleted file mode 100644 index 917a250ba..000000000 --- a/src/test/kotlin/com/pubnub/tests/AppTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.pubnub.tests - -import com.pubnub.api.PNConfiguration -import com.pubnub.api.PubNub -import com.pubnub.api.enums.PNLogVerbosity -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.util.* - -class AppTest { - - lateinit var pubnub: PubNub - - @BeforeEach - fun initPubnub() { - pubnub = PubNub( - PNConfiguration().apply { - subscribeKey = "demo-36" - publishKey = "demo-36" - logVerbosity = PNLogVerbosity.BODY - } - ) - } - - @Test - fun testPublish() { - pubnub.publish { - channel = UUID.randomUUID().toString() - message = UUID.randomUUID().toString() - }.sync().let { - Assertions.assertNotNull(it) - } - } - - @Test - fun testPublishWithHistory() { - val expectedChannel = UUID.randomUUID().toString() - val expectedCount = 3 - val expectedMessages = generateSequence { UUID.randomUUID().toString() }.take(expectedCount).toList() - - expectedMessages.forEach { - pubnub.publish { - channel = expectedChannel - message = it - }.sync().let { - assertNotNull(it) - } - } - - Thread.sleep(1000) - - pubnub.history { - channel = expectedChannel - }.sync().let { result -> - assertNotNull(result) - assertEquals(expectedCount, result!!.messages.size) - - assertEquals(result.messages.map { it.entry.asString }, expectedMessages) - - result.messages.forEach { - expectedMessages.contains(it.entry.asString) - } - } - - } -} \ No newline at end of file From 87700ff5a3f31f6a4d66a4f9191e539d6126a352 Mon Sep 17 00:00:00 2001 From: azurpn Date: Mon, 23 Mar 2020 21:25:29 +0100 Subject: [PATCH 16/98] Add mock tests --- .../presence/GetStateEndpointTest.kt | 100 ++++ .../endpoints/presence/HereNowEndpointTest.kt | 545 ++++++++++++++++++ 2 files changed, 645 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt new file mode 100644 index 000000000..67dc6dd5e --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt @@ -0,0 +1,100 @@ +package com.pubnub.api.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.BaseTest +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class GetStateEndpointTest : BaseTest() { + + @Test + fun testOneChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["testChannel"] + assertEquals(pubnub.mapper.elementToInt(ch1Data!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + } + + @Test + fun testOneChannelWithoutUUIDSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getState().apply { + channels = listOf("testChannel") + }.sync()!! + + val ch1Data = result.stateByUUID["testChannel"] + assertEquals(pubnub.mapper.elementToInt(ch1Data!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + } + + @Test + fun testFailedPayloadSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + "{ \"status\": 200, \"message\": \"OK\", \"payload\": \"age\" : 20, " + + "\"status\" : \"online\"}, \"service\": \"Presence\"}" + ) + ) + ) + + try { + pubnub.getState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync() + } catch (e: Exception) { + e as PubNubException + assertEquals(PubNubError.PARSING_ERROR, e.pubnubError) + println("e.errorMessage ${e.errorMessage}" ) + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt new file mode 100644 index 000000000..9d2b1f148 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt @@ -0,0 +1,545 @@ +package com.pubnub.api.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.anyUrl +import com.github.tomakehurst.wiremock.client.WireMock.findAll +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlMatching +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.BaseTest +import com.pubnub.api.PubNubError.SUBSCRIBE_KEY_MISSING +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType.PNHereNowOperation +import com.pubnub.api.models.consumer.presence.PNHereNowResult +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.atomic.AtomicInteger + +class HereNowEndpointTest : BaseTest() { + + @Test + fun testMultipleChannelStateSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")).willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "total_occupancy": 3, + "total_channels": 2, + "channels": { + "ch1": { + "occupancy": 1, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + } + ] + }, + "ch2": { + "occupancy": 2, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + }, + { + "uuid": "user3", + "state": { + "age": 30 + } + } + ] + } + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("ch1", "ch2") + includeState = true + }.sync()!! + + assertEquals(response.totalChannels, 2) + assertEquals(response.totalOccupancy, 3) + + assertEquals(response.channels["ch1"]!!.channelName, "ch1") + assertEquals(response.channels["ch1"]!!.occupancy, 1) + assertEquals(response.channels["ch1"]!!.occupants.size, 1) + assertEquals(response.channels["ch1"]!!.occupants[0].uuid, "user1") + assertEquals(response.channels["ch1"]!!.occupants[0].state.toString(), """{"age":10}""") + + assertEquals(response.channels["ch2"]!!.channelName, "ch2") + assertEquals(response.channels["ch2"]!!.occupancy, 2) + assertEquals(response.channels["ch2"]!!.occupants.size, 2) + assertEquals(response.channels["ch2"]!!.occupants[0].uuid, "user1") + assertEquals(response.channels["ch2"]!!.occupants[0].state.toString(), """{"age":10}""") + assertEquals(response.channels["ch2"]!!.occupants[1].uuid, "user3") + assertEquals(response.channels["ch2"]!!.occupants[1].state.toString(), """{"age":30}""") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("state").firstValue()) + } + + @Test + fun testMultipleChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "total_occupancy": 3, + "total_channels": 2, + "channels": { + "ch1": { + "occupancy": 1, + "uuids": [ + { + "uuid": "user1" + } + ] + }, + "ch2": { + "occupancy": 2, + "uuids": [ + { + "uuid": "user1" + }, + { + "uuid": "user3" + } + ] + } + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("ch1", "ch2") + includeState = true + }.sync()!! + + assertEquals(response.totalChannels, 2) + assertEquals(response.totalOccupancy, 3) + + assertEquals(response.channels["ch1"]!!.channelName, "ch1") + assertEquals(response.channels["ch1"]!!.occupancy, 1) + assertEquals(response.channels["ch1"]!!.occupants.size, 1) + assertEquals(response.channels["ch1"]!!.occupants[0].uuid, "user1") + assertNull(response.channels["ch1"]!!.occupants[0].state) + + assertEquals(response.channels["ch2"]!!.channelName, "ch2") + assertEquals(response.channels["ch2"]!!.occupancy, 2) + assertEquals(response.channels["ch2"]!!.occupants.size, 2) + assertEquals(response.channels["ch2"]!!.occupants[0].uuid, "user1") + assertNull(response.channels["ch2"]!!.occupants[0].state) + assertEquals(response.channels["ch2"]!!.occupants[1].uuid, "user3") + assertNull(response.channels["ch2"]!!.occupants[1].state) + + val requests = findAll(getRequestedFor(anyUrl())) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("state").firstValue()) + } + + @Test + fun testMultipleChannelWithoutStateSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1,game2")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": { + "game1": { + "uuids": [ + "a3ffd012-a3b9-478c-8705-64089f24d71e" + ], + "occupancy": 1 + } + }, + "total_channels": 1, + "total_occupancy": 1 + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("game1", "game2") + includeState = false + }.sync()!! + + assertEquals(response.totalChannels, 1) + assertEquals(response.totalOccupancy, 1) + + assertEquals(response.channels["game1"]!!.channelName, "game1") + assertEquals(response.channels["game1"]!!.occupancy, 1) + assertEquals(response.channels["game1"]!!.occupants.size, 1) + assertEquals(response.channels["game1"]!!.occupants[0].uuid, "a3ffd012-a3b9-478c-8705-64089f24d71e") + assertNull(response.channels["game1"]!!.occupants[0].state) + } + + @Test + fun testMultipleChannelWithoutStateUUIDsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1,game2")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": { + "game1": { + "occupancy": 1 + } + }, + "total_channels": 1, + "total_occupancy": 1 + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("game1", "game2") + includeState = false + includeUUIDs = false + }.sync()!! + + assertEquals(response.totalChannels, 1) + assertEquals(response.totalOccupancy, 1) + + assertEquals(response.channels["game1"]!!.channelName, "game1") + assertEquals(response.channels["game1"]!!.occupancy, 1) + assertTrue(response.channels["game1"]!!.occupants.isEmpty()) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("disable_uuids").firstValue()) + } + + @Test + fun testSingularChannelWithoutStateUUIDsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "occupancy": 3 + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("game1") + includeUUIDs = false + includeUUIDs = false + }.sync()!! + + println(response) + + assertEquals(response.totalChannels, 1) + assertEquals(response.totalOccupancy, 3) + println("response.channels: ${response.channels}") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("disable_uuids").firstValue()) + } + + @Test + fun testSingularChannelWithoutStateSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "uuids": [ + "a3ffd012-a3b9-478c-8705-64089f24d71e" + ], + "occupancy": 1 + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("game1") + includeState = false + }.sync()!! + + assertEquals(response.totalChannels, 1) + assertEquals(response.totalOccupancy, 1) + assertEquals(response.channels.size, 1) + assertEquals(response.channels["game1"]!!.occupancy, 1) + assertEquals(response.channels["game1"]!!.occupants.size, 1) + assertEquals(response.channels["game1"]!!.occupants[0].uuid, "a3ffd012-a3b9-478c-8705-64089f24d71e") + assertEquals(response.channels["game1"]!!.occupants[0].state, null) + } + + @Test + fun testSingularChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "uuids": [ + { + "uuid": "a3ffd012-a3b9-478c-8705-64089f24d71e", + "state": { + "age": 10 + } + } + ], + "occupancy": 1 + } + """.trimIndent() + ) + ) + ) + val response = pubnub.hereNow().apply { + channels = listOf("game1") + includeState = true + }.sync()!! + + assertEquals(response.totalChannels, 1) + assertEquals(response.totalOccupancy, 1) + assertEquals(response.channels.size, 1) + assertEquals(response.channels["game1"]!!.occupancy, 1) + assertEquals(response.channels["game1"]!!.occupants.size, 1) + assertEquals(response.channels["game1"]!!.occupants[0].uuid, "a3ffd012-a3b9-478c-8705-64089f24d71e") + assertEquals(response.channels["game1"]!!.occupants[0].state.toString(), """{"age":10}""") + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("state").firstValue()) + } + + @Test + fun testSingularChannelAndGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": {}, + "total_channels": 0, + "total_occupancy": 0 + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.hereNow().apply { + channels = listOf("game1") + channelGroups = listOf("grp1") + includeState = true + }.sync()!! + + assertEquals(response.totalOccupancy, 0) + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "total_occupancy": 3, + "total_channels": 2, + "channels": { + "ch1": { + "occupancy": 1, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + } + ] + }, + "ch2": { + "occupancy": 2, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + }, + { + "uuid": "user3", + "state": { + "age": 30 + } + } + ] + } + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.hereNow().apply { + channels = listOf("ch1", "ch2") + includeState = true + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "total_occupancy": 3, + "total_channels": 2, + "channels": { + "ch1": { + "occupancy": 1, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + } + ] + }, + "ch2": { + "occupancy": 2, + "uuids": [ + { + "uuid": "user1", + "state": { + "age": 10 + } + }, + { + "uuid": "user3", + "state": { + "age": 30 + } + } + ] + } + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.hereNow().async { _, status -> + if (status.operation == PNHereNowOperation) { + atomic.incrementAndGet(); + } + } + + Awaitility.await().atMost(5, SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testEmptySubKeySync() { + pubnub.configuration.subscribeKey = "" + + var response: PNHereNowResult? = null + try { + response = pubnub.hereNow().apply { + channels = listOf("ch1", "ch2") + includeState = true + }.sync() + } catch (e: Exception) { + assertNull(response) + assertTrue((e as PubNubException).pubnubError == SUBSCRIBE_KEY_MISSING) + } + } + +} \ No newline at end of file From 44e023341c0eb88f1ed0d61fdcb5bd422af19431 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:24:01 +0200 Subject: [PATCH 17/98] Cleanup all endpoints --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 164 +++++++++++++----- .../com/pubnub/api/builder/PubSubBuilders.kt | 6 +- .../kotlin/com/pubnub/api/endpoints/Time.kt | 5 +- .../pubnub/api/endpoints/presence/GetState.kt | 5 - .../api/endpoints/presence/Heartbeat.kt | 14 +- .../pubnub/api/endpoints/presence/HereNow.kt | 10 +- .../pubnub/api/endpoints/presence/Leave.kt | 20 +-- .../pubnub/api/endpoints/presence/SetState.kt | 17 +- .../pubnub/api/endpoints/presence/WhereNow.kt | 11 +- .../api/endpoints/{ => pubsub}/Publish.kt | 22 +-- .../pubnub/api/endpoints/pubsub/Subscribe.kt | 19 +- 11 files changed, 153 insertions(+), 140 deletions(-) rename src/main/kotlin/com/pubnub/api/endpoints/{ => pubsub}/Publish.kt (84%) diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index d0a331f3d..57093ed5e 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -3,17 +3,8 @@ package com.pubnub.api import com.google.gson.JsonElement import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory -import com.pubnub.api.enums.PNStatusCategory.PNAccessDeniedCategory -import com.pubnub.api.enums.PNStatusCategory.PNAcknowledgmentCategory -import com.pubnub.api.enums.PNStatusCategory.PNBadRequestCategory -import com.pubnub.api.enums.PNStatusCategory.PNCancelledCategory -import com.pubnub.api.enums.PNStatusCategory.PNMalformedResponseCategory -import com.pubnub.api.enums.PNStatusCategory.PNNotFoundCategory -import com.pubnub.api.enums.PNStatusCategory.PNTimeoutCategory -import com.pubnub.api.enums.PNStatusCategory.PNUnexpectedDisconnectCategory -import com.pubnub.api.enums.PNStatusCategory.PNUnknownCategory +import com.pubnub.api.enums.PNStatusCategory.* import com.pubnub.api.models.consumer.PNStatus -import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -21,9 +12,9 @@ import java.io.IOException import java.net.ConnectException import java.net.SocketTimeoutException import java.net.UnknownHostException -import java.util.HashMap +import java.util.* -abstract class Endpoint(internal val pubnub: PubNub) { +abstract class Endpoint(protected val pubnub: PubNub) { companion object { private const val SERVER_RESPONSE_SUCCESS = 200 @@ -44,8 +35,9 @@ abstract class Endpoint(internal val pubnub: PubNub) { val response = try { call.execute() - } catch (e: IOException) { + } catch (e: Exception) { throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() affectedCall = call } } @@ -53,13 +45,13 @@ abstract class Endpoint(internal val pubnub: PubNub) { when { response.isSuccessful -> { storeRequestLatency(response) - return createResponse(response) + return checkAndCreateResponse(response) } else -> { val (errorString, errorJson) = extractErrorBody(response) throw PubNubException(PubNubError.HTTP_ERROR).apply { errorMessage = errorString - jso = errorJson + jso = errorJson.toString() statusCode = response.code() affectedCall = call } @@ -93,7 +85,7 @@ abstract class Endpoint(internal val pubnub: PubNub) { // query params storeRequestLatency(response) try { - Triple(PNAcknowledgmentCategory, createResponse(response), null) + Triple(PNAcknowledgmentCategory, checkAndCreateResponse(response), null) } catch (e: PubNubException) { Triple(PNMalformedResponseCategory, null, e) }.let { @@ -111,7 +103,7 @@ abstract class Endpoint(internal val pubnub: PubNub) { val (errorString, errorJson) = extractErrorBody(response) val exception = with(PubNubException(errorString)) { - jso = errorJson + jso = errorJson.toString() statusCode = response.code() this } @@ -124,9 +116,10 @@ abstract class Endpoint(internal val pubnub: PubNub) { callback.invoke( null, createStatusResponse( - pnStatusCategory, - response, - exception + category = pnStatusCategory, + response = response, + exception = exception, + errorBody = errorJson ) ) return @@ -142,7 +135,7 @@ abstract class Endpoint(internal val pubnub: PubNub) { lateinit var pnStatusCategory: PNStatusCategory - val pubnubException = PubNubException(t.message) + val pubnubException = PubNubException(t.toString()) try { throw t @@ -155,6 +148,12 @@ abstract class Endpoint(internal val pubnub: PubNub) { } catch (socketTimeoutException: SocketTimeoutException) { pubnubException.pubnubError = PubNubError.SUBSCRIBE_TIMEOUT pnStatusCategory = PNTimeoutCategory + } catch (ioException: IOException) { + pubnubException.pubnubError = PubNubError.PARSING_ERROR + pnStatusCategory = PNMalformedResponseCategory + } catch (ioException: IllegalStateException) { + pubnubException.pubnubError = PubNubError.PARSING_ERROR + pnStatusCategory = PNMalformedResponseCategory } catch (throwable1: Throwable) { pubnubException.pubnubError = PubNubError.HTTP_ERROR pnStatusCategory = if (call.isCanceled) { @@ -194,14 +193,27 @@ abstract class Endpoint(internal val pubnub: PubNub) { return encodedParams } + protected fun appendInclusionParams(map: MutableMap, params: List>) { + /*if (params.isEmpty()) { + return + }*/ + map["include"] = params.toCsv() + } + private fun createBaseParams(): HashMap { val map = hashMapOf( "pnsdk" to "PubNub-Kotlin/${pubnub.version}", - "uuid" to pubnub.configuration.uuid, - "instanceid" to pubnub.instanceId, - "requestid" to pubnub.requestId() + "uuid" to pubnub.configuration.uuid ) + if (pubnub.configuration.includeInstanceIdentifier) { + map["instanceid"] = pubnub.instanceId + } + + if (pubnub.configuration.includeRequestIdentifier) { + map["requestid"] = pubnub.requestId() + } + if (isAuthRequired() && pubnub.configuration.isAuthKeyValid()) { map["auth"] = pubnub.configuration.authKey } @@ -226,7 +238,8 @@ abstract class Endpoint(internal val pubnub: PubNub) { private fun createStatusResponse( category: PNStatusCategory, response: Response? = null, - exception: PubNubException? = null + exception: PubNubException? = null, + errorBody: JsonElement? = null ): PNStatus { val pnStatus = PNStatus( @@ -250,9 +263,61 @@ abstract class Endpoint(internal val pubnub: PubNub) { } } + val errorChannels = mutableListOf() + val errorGroups = mutableListOf() + + if (errorBody != null) { + if (pubnub.mapper.isJsonObject(errorBody) && pubnub.mapper.hasField(errorBody, "payload")) { + + val payloadBody = pubnub.mapper.getField(errorBody, "payload")!! + + if (pubnub.mapper.hasField(payloadBody, "channels")) { + val iterator = pubnub.mapper.getArrayIterator(payloadBody, "channels") + while (iterator.hasNext()) { + errorChannels.add(pubnub.mapper.elementToString(iterator.next())!!) + } + } + + if (pubnub.mapper.hasField(payloadBody, "channel-groups")) { + val iterator = pubnub.mapper.getArrayIterator(payloadBody, "channel-groups") + while (iterator.hasNext()) { + val node = iterator.next() + + val channelGroupName = pubnub.mapper.elementToString(node)!!.let { + if (it.first().toString() == ":") { + it.substring(1) + } else { + it + } + } + + errorGroups.add(channelGroupName) + } + } + } + } + + pnStatus.affectedChannels = + if (errorChannels.isNotEmpty()) { + errorChannels + } else { + try { + getAffectedChannels() + } catch (e: UninitializedPropertyAccessException) { + emptyList() + } + } - pnStatus.affectedChannels = getAffectedChannels() - pnStatus.affectedChannelGroups = getAffectedChannelGroups() + pnStatus.affectedChannelGroups = + if (errorGroups.isNotEmpty()) { + errorGroups + } else { + try { + getAffectedChannelGroups() + } catch (e: UninitializedPropertyAccessException) { + emptyList() + } + } return pnStatus } @@ -278,9 +343,30 @@ abstract class Endpoint(internal val pubnub: PubNub) { return errorBodyString to errorBodyJson } - protected abstract fun getAffectedChannels(): List + private fun checkAndCreateResponse(input: Response): Output? { + try { + return createResponse(input) + } catch (pubnubException: PubNubException) { + throw pubnubException.apply { + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + affectedCall = call + } + } catch (e: KotlinNullPointerException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } + } + + // protected abstract fun getAffectedChannels(): List + // protected abstract fun getAffectedChannelGroups(): List - protected abstract fun getAffectedChannelGroups(): List + protected open fun getAffectedChannels() = emptyList() + protected open fun getAffectedChannelGroups(): List = emptyList() protected open fun validateParams() { if (isSubKeyRequired() && !pubnub.configuration.isSubscribeKeyValid()) { @@ -291,21 +377,21 @@ abstract class Endpoint(internal val pubnub: PubNub) { } } - @Throws(PubNubException::class) protected abstract fun doWork(queryParams: HashMap): Call - - @Throws(PubNubException::class) protected abstract fun createResponse(input: Response): Output? protected abstract fun operationType(): PNOperationType - protected abstract fun isSubKeyRequired(): Boolean - protected abstract fun isPubKeyRequired(): Boolean - protected abstract fun isAuthRequired(): Boolean + protected open fun isSubKeyRequired() = true + protected open fun isPubKeyRequired() = false + protected open fun isAuthRequired() = true } -internal fun Response>.throwIfEmpty() { - if (body() == null || body()?.payload == null) { - throw PubNubException(PubNubError.PARSING_ERROR) +internal fun HashMap.encodeAuth(): HashMap { + this["auth"]?.let { + this["auth"] = it.pnUrlEncode() } -} \ No newline at end of file + return this +} + +internal fun String.pnUrlEncode() = PubNubUtil.urlEncode(this) diff --git a/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt index 141cbed3f..5b36964c2 100644 --- a/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt +++ b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt @@ -10,10 +10,10 @@ abstract class PubSubBuilder( abstract fun execute() } -internal class PresenceBuilder( +class PresenceBuilder( subscriptionManager: SubscriptionManager, - channels: List, - channelGroups: List, + channels: List = emptyList(), + channelGroups: List = emptyList(), internal var connected: Boolean = false ) : PubSubBuilder(subscriptionManager, channels, channelGroups) { diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Time.kt b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt index 367927706..9d3886d0d 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/Time.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/Time.kt @@ -16,15 +16,12 @@ class Time(pubnub: PubNub) : Endpoint, PNTimeResult>(pubnub) { pubnub.retrofitManager.timeService.fetchTime(queryParams) override fun createResponse(input: Response>): PNTimeResult? { - return input.body()?.let { - PNTimeResult(it[0]) - } + return PNTimeResult(input.body()!![0]) } override fun operationType() = PNOperationType.PNTimeOperation override fun isAuthRequired() = false override fun isSubKeyRequired() = false - override fun isPubKeyRequired() = false } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt index 35da8629a..7b6c6a32b 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt @@ -62,12 +62,7 @@ class GetState(pubnub: PubNub) : Endpoint, PNGetStateResul ) } - override fun operationType() = PNOperationType.PNGetState - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt index cad652f32..1b6afc0ca 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt @@ -8,16 +8,15 @@ import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* internal class Heartbeat( pubnub: PubNub, val channels: List = listOf(), val channelGroups: List = listOf() -) : Endpoint, Boolean>(pubnub) { +) : Endpoint, Boolean>(pubnub) { override fun getAffectedChannels() = channels - override fun getAffectedChannelGroups() = channelGroups override fun validateParams() { @@ -27,7 +26,7 @@ internal class Heartbeat( } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call> { queryParams["heartbeat"] = pubnub.configuration.presenceTimeout.toString() if (channelGroups.isNotEmpty()) { @@ -49,13 +48,8 @@ internal class Heartbeat( ) } - override fun createResponse(input: Response>) = true + override fun createResponse(input: Response>) = true override fun operationType() = PNOperationType.PNHeartbeatOperation - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true - - } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt index 2d6fcf88e..4be658f91 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/HereNow.kt @@ -24,9 +24,8 @@ class HereNow(pubnub: PubNub) : Endpoint, PNHereNowResult> // todo try here now without sub key - override fun getAffectedChannels(): List = channels - - override fun getAffectedChannelGroups(): List = channelGroups + override fun getAffectedChannels() = channels + override fun getAffectedChannelGroups() = channelGroups override fun doWork(queryParams: HashMap): Call> { if (includeState) { @@ -131,9 +130,4 @@ class HereNow(pubnub: PubNub) : Endpoint, PNHereNowResult> } override fun operationType() = PNOperationType.PNHereNowOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true - } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt index 7879c0a7b..9df58834a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt @@ -1,17 +1,12 @@ package com.pubnub.api.endpoints.presence -import com.pubnub.api.Endpoint -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException +import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.models.server.Envelope -import com.pubnub.api.toCsv import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* -internal class Leave(pubnub: PubNub) : Endpoint, Boolean>(pubnub) { +internal class Leave(pubnub: PubNub) : Endpoint(pubnub) { var channels = emptyList() var channelGroups = emptyList() @@ -24,10 +19,9 @@ internal class Leave(pubnub: PubNub) : Endpoint, Boolean>(pubnub) { } override fun getAffectedChannels() = channels - override fun getAffectedChannelGroups() = channelGroups - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { queryParams["channel-group"] = channelGroups.toCsv() return pubnub.retrofitManager.presenceService.leave( @@ -37,13 +31,9 @@ internal class Leave(pubnub: PubNub) : Endpoint, Boolean>(pubnub) { ) } - override fun createResponse(input: Response>) = true + override fun createResponse(input: Response) = true override fun operationType() = PNOperationType.PNUnsubscribeOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt index 7f01e7f6b..e2c9e0d19 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt @@ -1,20 +1,14 @@ package com.pubnub.api.endpoints.presence import com.google.gson.JsonElement -import com.pubnub.api.Endpoint -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException -import com.pubnub.api.PubNubUtil +import com.pubnub.api.* import com.pubnub.api.builder.StateOperation import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.presence.PNSetStateResult import com.pubnub.api.models.server.Envelope -import com.pubnub.api.throwIfEmpty -import com.pubnub.api.toCsv import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* class SetState(pubnub: PubNub) : Endpoint, PNSetStateResult>(pubnub) { @@ -25,7 +19,6 @@ class SetState(pubnub: PubNub) : lateinit var state: Any override fun getAffectedChannels() = channels - override fun getAffectedChannelGroups() = channelGroups override fun validateParams() { @@ -66,18 +59,12 @@ class SetState(pubnub: PubNub) : } override fun createResponse(input: Response>): PNSetStateResult? { - input.throwIfEmpty() return PNSetStateResult( state = input.body()!!.payload ) } - override fun operationType() = PNOperationType.PNSetStateOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt index 7e464f2c5..a1099b24d 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/WhereNow.kt @@ -6,18 +6,14 @@ import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.presence.PNWhereNowResult import com.pubnub.api.models.server.Envelope import com.pubnub.api.models.server.presence.WhereNowPayload -import com.pubnub.api.throwIfEmpty import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* class WhereNow(pubnub: PubNub) : Endpoint, PNWhereNowResult>(pubnub) { var uuid = pubnub.configuration.uuid - override fun getAffectedChannels() = emptyList() - override fun getAffectedChannelGroups() = emptyList() - override fun doWork(queryParams: HashMap): Call> { return pubnub.retrofitManager.presenceService.whereNow( pubnub.configuration.subscribeKey, @@ -27,13 +23,8 @@ class WhereNow(pubnub: PubNub) : Endpoint, PNWhereNowR } override fun createResponse(input: Response>): PNWhereNowResult? { - input.throwIfEmpty() return PNWhereNowResult(input.body()!!.payload!!.channels) } override fun operationType() = PNOperationType.PNWhereNowOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt similarity index 84% rename from src/main/kotlin/com/pubnub/api/endpoints/Publish.kt rename to src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt index fc55ecce2..a26130ef5 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/Publish.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt @@ -1,10 +1,6 @@ -package com.pubnub.api.endpoints +package com.pubnub.api.endpoints.pubsub -import com.pubnub.api.Endpoint -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException -import com.pubnub.api.PubNubUtil +import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.vendor.Crypto @@ -27,7 +23,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { override fun validateParams() { super.validateParams() - if (!isChannelValid()) { + if (!isChannelValid() || channel.isBlank()) { throw PubNubException(PubNubError.CHANNEL_MISSING) } if (!isMessageValid()) { @@ -37,8 +33,6 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { override fun getAffectedChannels() = listOf(channel) - override fun getAffectedChannelGroups() = emptyList() - override fun doWork(queryParams: HashMap): Call> { var stringifiedMessage = pubnub.mapper.toJson(message) @@ -47,7 +41,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { queryParams["meta"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(meta)) } - if (shouldStore) queryParams["store"] = "1" + queryParams["store"] = if (shouldStore) "1" else "0" ttl?.let { queryParams["ttl"] = it.toString() } @@ -97,15 +91,13 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { } override fun createResponse(input: Response>): PNPublishResult? { - return input.body()?.let { - PNPublishResult(it[2].toString().toLong()) - } + return PNPublishResult( + timetoken = input.body()!![2].toString().toLong() + ) } override fun operationType() = PNOperationType.PNPublishOperation - override fun isSubKeyRequired() = true override fun isPubKeyRequired() = true - override fun isAuthRequired() = true } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt index b551d6a0a..1ba04a60a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt @@ -1,16 +1,11 @@ package com.pubnub.api.endpoints.pubsub -import com.pubnub.api.Endpoint -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException -import com.pubnub.api.PubNubUtil +import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.server.SubscribeEnvelope -import com.pubnub.api.toCsv import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* class Subscribe(pubnub: PubNub) : Endpoint(pubnub) { @@ -65,16 +60,8 @@ class Subscribe(pubnub: PubNub) : Endpoint } override fun createResponse(input: Response): SubscribeEnvelope? { - if (input.body() == null) { - throw PubNubException(PubNubError.PARSING_ERROR) - } else { - return input.body() - } + return input.body() } override fun operationType() = PNOperationType.PNSubscribeOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } \ No newline at end of file From 7bb7a05248b6c319d6dcacabfdbec1a9b6ae2362 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:26:06 +0200 Subject: [PATCH 18/98] Improve BasePathManager --- .../pubnub/api/managers/BasePathManager.kt | 62 ++++++++- .../api/managers/BasePathManagerTest.kt | 118 ++++++++++++++++++ 2 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt diff --git a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt index c6d66a195..bf0622525 100644 --- a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt @@ -1,15 +1,65 @@ package com.pubnub.api.managers -import com.pubnub.api.PubNub +import com.pubnub.api.PNConfiguration -class BasePathManager(val pubnub: PubNub) { +class BasePathManager(val config: PNConfiguration) { + + /** + * for cache busting, the current subdomain number used. + */ + private var currentSubdomain = 1 + + /** + * if using cache busting, this is the max number of subdomains that are supported. + */ + private val MAX_SUBDOMAIN = 20 + + /** + * default subdomain used if cache busting is disabled. + */ + + private val DEFAULT_SUBDOMAIN = "ps" + /** + * default base path if a custom one is not provided. + */ + + private val DEFAULT_BASE_PATH = "pndsn.com" fun basePath(): String { - return StringBuilder("http") - .append(if (pubnub.configuration.secure) "s" else "") + val basePathBuilder = StringBuilder("http") + .append(if (config.secure) "s" else "") .append("://") - .append(pubnub.configuration.origin) - .toString() + + when { + config.isOriginValid() -> { + basePathBuilder.append(config.origin) + } + config.cacheBusting -> { + basePathBuilder + .append("ps") + .append(currentSubdomain) + .append(".") + .append(DEFAULT_BASE_PATH) + + incrementSubdomain() + } + else -> { + basePathBuilder + .append(DEFAULT_SUBDOMAIN) + .append(".") + .append(DEFAULT_BASE_PATH) + } + } + + return basePathBuilder.toString() + } + + private fun incrementSubdomain() { + if (currentSubdomain == MAX_SUBDOMAIN) { + currentSubdomain = 1 + } else { + currentSubdomain++ + } } } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt b/src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt new file mode 100644 index 000000000..550a36909 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt @@ -0,0 +1,118 @@ +package com.pubnub.api.managers + +import com.pubnub.api.BaseTest +import com.pubnub.api.PNConfiguration +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class BasePathManagerTest : BaseTest() { + + lateinit var config: PNConfiguration + + override fun onBefore() { + config = PNConfiguration() + } + + @Test + fun stdOriginNotSecure() { + config.secure = false + val basePathManager = BasePathManager(config) + assertEquals("http://ps.pndsn.com", basePathManager.basePath()) + } + + @Test + fun stdOriginSecure() { + config.secure = true + val basePathManager = BasePathManager(config) + assertEquals("https://ps.pndsn.com", basePathManager.basePath()) + } + + @Test + fun customOriginNotSecure() { + config.origin = "custom.origin.com" + config.secure = false + val basePathManager = BasePathManager(config) + assertEquals("http://custom.origin.com", basePathManager.basePath()) + } + + @Test + fun customOriginSecure() { + config.origin = "custom.origin.com" + config.secure = true + val basePathManager = BasePathManager(config) + assertEquals("https://custom.origin.com", basePathManager.basePath()) + } + + @Test + fun customOriginNotSecureWithCacheBusting() { + config.origin = "custom.origin.com" + config.cacheBusting = true + config.secure = false + val basePathManager = BasePathManager(config) + assertEquals("http://custom.origin.com", basePathManager.basePath()) + } + + @Test + fun customOriginSecureWithCacheBusting() { + config.origin = "custom.origin.com" + config.secure = true + config.cacheBusting = true + val basePathManager = BasePathManager(config) + assertEquals("https://custom.origin.com", basePathManager.basePath()) + } + + @Test + fun cacheBustingNotSecure() { + config.cacheBusting = true + config.secure = false + val basePathManager = BasePathManager(config) + assertEquals("http://ps1.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps2.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps3.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps4.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps5.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps6.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps7.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps8.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps9.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps10.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps11.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps12.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps13.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps14.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps15.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps16.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps17.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps18.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps19.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps20.pndsn.com", basePathManager.basePath()) + assertEquals("http://ps1.pndsn.com", basePathManager.basePath()) + } + + @Test + fun cacheBustingSecure() { + config.cacheBusting = true + val basePathManager = BasePathManager(config) + assertEquals("https://ps1.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps2.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps3.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps4.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps5.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps6.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps7.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps8.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps9.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps10.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps11.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps12.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps13.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps14.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps15.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps16.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps17.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps18.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps19.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps20.pndsn.com", basePathManager.basePath()) + assertEquals("https://ps1.pndsn.com", basePathManager.basePath()) + } +} \ No newline at end of file From 32923374c6ad0b04fbc0017634e0316b18e3ec2c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:27:53 +0200 Subject: [PATCH 19/98] Implement Channel Groups --- .../channel_groups/AddChannelChannelGroup.kt | 47 ++++ .../channel_groups/AllChannelsChannelGroup.kt | 47 ++++ .../channel_groups/DeleteChannelGroup.kt | 45 ++++ .../channel_groups/ListAllChannelGroup.kt | 30 +++ .../RemoveChannelChannelGroup.kt | 49 ++++ .../api/services/ChannelGroupService.kt | 45 ++++ .../AddChannelChannelGroupEndpointTest.kt | 207 +++++++++++++++++ .../AllChannelsChannelGroupEndpointTest.kt | 215 ++++++++++++++++++ .../DeleteChannelGroupEndpointTest.kt | 176 ++++++++++++++ .../ListAllChannelGroupEndpointTest.kt | 118 ++++++++++ .../RemoveChannelChannelGroupEndpointTest.kt | 132 +++++++++++ 11 files changed, 1111 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt new file mode 100644 index 000000000..a398085db --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt @@ -0,0 +1,47 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAddChannelResult +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class AddChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsAddChannelResult>(pubnub) { + + lateinit var channelGroup: String + lateinit var channels: List + + override fun getAffectedChannels() = channels + override fun getAffectedChannelGroups() = listOf(channelGroup) + + override fun validateParams() { + super.validateParams() + if (!::channelGroup.isInitialized || channelGroup.isBlank()) { + throw PubNubException(PubNubError.GROUP_MISSING) + } + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (channels.isNotEmpty()) { + queryParams["add"] = channels.toCsv() + } + + return pubnub.retrofitManager.channelGroupService + .addChannelChannelGroup( + pubnub.configuration.subscribeKey, + channelGroup, + queryParams + ) + } + + override fun createResponse(input: Response>): PNChannelGroupsAddChannelResult? { + return PNChannelGroupsAddChannelResult() + } + + override fun operationType() = PNOperationType.PNAddChannelsToGroupOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt new file mode 100644 index 000000000..1f6e1d7ee --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt @@ -0,0 +1,47 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAllChannelsResult +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class AllChannelsChannelGroup(pubnub: PubNub) : + Endpoint>, PNChannelGroupsAllChannelsResult>(pubnub) { + + lateinit var channelGroup: String + + override fun getAffectedChannelGroups() = listOf(channelGroup) + + override fun validateParams() { + super.validateParams() + if (!::channelGroup.isInitialized || channelGroup.isBlank()) { + throw PubNubException(PubNubError.GROUP_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call>> { + return pubnub.retrofitManager.channelGroupService + .allChannelsChannelGroup( + pubnub.configuration.subscribeKey, + channelGroup, + queryParams + ) + } + + override fun createResponse(input: Response>>): PNChannelGroupsAllChannelsResult? { + // todo test dot of null + return PNChannelGroupsAllChannelsResult().apply { + input.body()!!.payload!!["channels"]?.let { + channels = it as List + } + } + } + + override fun operationType() = PNOperationType.PNChannelsForGroupOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt new file mode 100644 index 000000000..9296f88eb --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt @@ -0,0 +1,45 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class DeleteChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsDeleteGroupResult>(pubnub) { + + lateinit var channelGroup: String + + override fun validateParams() { + super.validateParams() + if (!::channelGroup.isInitialized || channelGroup.isBlank()) { + throw PubNubException(PubNubError.GROUP_MISSING) + } + } + + override fun getAffectedChannelGroups() = listOf(channelGroup) + + override fun doWork(queryParams: HashMap): Call> { + return pubnub.retrofitManager.channelGroupService + .deleteChannelGroup( + pubnub.configuration.subscribeKey, + channelGroup, + queryParams + ) + } + + override fun createResponse(input: Response>): PNChannelGroupsDeleteGroupResult? { + if (input.body() == null) { + throw PubNubException(PubNubError.PARSING_ERROR) + } + return PNChannelGroupsDeleteGroupResult() + } + + override fun operationType() = PNOperationType.PNRemoveGroupOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt new file mode 100644 index 000000000..49afd4d9c --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt @@ -0,0 +1,30 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.google.gson.Gson +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class ListAllChannelGroup(pubnub: PubNub) : Endpoint>, PNChannelGroupsListAllResult>(pubnub) { + + override fun doWork(queryParams: HashMap): Call>> { + return pubnub.retrofitManager.channelGroupService + .listAllChannelGroup( + pubnub.configuration.subscribeKey, + queryParams + ) + } + + override fun createResponse(input: Response>>): PNChannelGroupsListAllResult? { + return PNChannelGroupsListAllResult().apply { + groups = input.body()!!.payload!!["groups"] as List + } + } + + override fun operationType() = PNOperationType.PNChannelGroupsOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt new file mode 100644 index 000000000..d3565246a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class RemoveChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsRemoveChannelResult>(pubnub) { + + lateinit var channelGroup: String + lateinit var channels: List + + override fun getAffectedChannels() = channels + + override fun getAffectedChannelGroups() = listOf(channelGroup) + + override fun validateParams() { + super.validateParams() + if (!::channelGroup.isInitialized || channelGroup.isBlank()) { + throw PubNubException(PubNubError.GROUP_MISSING) + } + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (channels.isNotEmpty()) { + queryParams["remove"] = channels.toCsv() + } + + return pubnub.retrofitManager.channelGroupService + .removeChannel( + pubnub.configuration.subscribeKey, + channelGroup, + queryParams + ) + } + + override fun createResponse(input: Response>): PNChannelGroupsRemoveChannelResult? { + return PNChannelGroupsRemoveChannelResult() + } + + override fun operationType() = PNOperationType.PNRemoveChannelsFromGroupOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt b/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt new file mode 100644 index 000000000..adf0953ae --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt @@ -0,0 +1,45 @@ +package com.pubnub.api.services + +import com.pubnub.api.models.server.Envelope +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +internal interface ChannelGroupService { + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group") + fun listAllChannelGroup( + @Path("subKey") subKey: String, + @QueryMap options: Map + ): Call>> + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + fun allChannelsChannelGroup( + @Path("subKey") subKey: String, + @Path("group") group: String, + @QueryMap options: Map + ): Call>> + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + fun addChannelChannelGroup( + @Path("subKey") subKey: String, + @Path("group") group: String, + @QueryMap options: Map + ): Call> + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + fun removeChannel( + @Path("subKey") subKey: String, + @Path("group") group: String, + @QueryMap options: Map + ): Call> + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}/remove") + fun deleteChannelGroup( + @Path("subKey") subKey: String, + @Path("group") group: String, + @QueryMap options: Map + ): Call> + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt new file mode 100644 index 000000000..d6753d7ab --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt @@ -0,0 +1,207 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class AddChannelChannelGroupEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson()) + ) + + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.sync()!! + } + + @Test + fun testSyncGroupMissing() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson()) + ) + + try { + pubnub.addChannelsToChannelGroup().apply { + channels = listOf("ch1", "ch2") + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testSyncGroupIsEmpty() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(emptyJson()) + ) + + try { + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "" + channels = listOf("ch1", "ch2") + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testSyncChannelMissing() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(emptyJson()) + ) + + try { + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + }.sync() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testIsSubRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson()) + ) + + pubnub.configuration.subscribeKey = " " + + try { + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson()) + ) + pubnub.configuration.authKey = "myKey" + + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson()) + ) + + val atomic = AtomicInteger(0) + + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.async { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAddChannelsToGroupOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + assertTrue(status.affectedChannels == listOf("ch1", "ch2")) + assertTrue(status.affectedChannelGroups == listOf("groupA")) + atomic.incrementAndGet() + } + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testErrorBodyForbidden() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .withQueryParam("add", matching("ch1,ch2")) + .willReturn(emptyJson().withStatus(403)) + ) + + val atomic = AtomicInteger(0) + + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.async { _, status -> + assertTrue(status.error) + assertEquals(PNOperationType.PNAddChannelsToGroupOperation, status.operation) + assertEquals(PNStatusCategory.PNAccessDeniedCategory, status.category) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(emptyJson()) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.addChannelsToChannelGroup().apply { + channelGroup = "groupA" + channels = listOf("ch1", "ch2") + }.async { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAddChannelsToGroupOperation, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_cg", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt new file mode 100644 index 000000000..737edcf5b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt @@ -0,0 +1,215 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import org.awaitility.Awaitility +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class AllChannelsChannelGroupEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.listChannelsForChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + + assertThat(response.channels, Matchers.contains("a", "b")) + } + + @Test + fun testSyncMissingGroup() { + try { + pubnub.listChannelsForChannelGroup().apply { + + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testSyncEmptyGroup() { + try { + pubnub.listChannelsForChannelGroup().apply { + channelGroup = " " + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.authKey = "myKey" + + pubnub.listChannelsForChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.listChannelsForChannelGroup().apply { + channelGroup = "groupA" + }.async { result, status -> + println(status) + assertFalse(status.error) + assertEquals(PNOperationType.PNChannelsForGroupOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + assertTrue(status.affectedChannels.isEmpty()) + assertEquals(listOf("groupA"), status.affectedChannelGroups) + assertEquals(listOf("a", "b"), result!!.channels) + atomic.incrementAndGet() + } + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testIsSubRequiredSuccessSync() { + pubnub.configuration.subscribeKey = " " + try { + pubnub.listChannelsForChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + e.printStackTrace() + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.listChannelsForChannelGroup().apply { + channelGroup = "groupA" + }.async { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNChannelsForGroupOperation, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_cg", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt new file mode 100644 index 000000000..4f454bb92 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt @@ -0,0 +1,176 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class DeleteChannelGroupEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() + ) + ) + ) + + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + } + + @Test + fun testSyncMissingGroup() { + try { + pubnub.deleteChannelGroup().apply { + + }.sync()!! + } catch (e: PubNubException) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testSyncEmptyGroup() { + stubFor(any(anyUrl()).willReturn(aResponse())) + + try { + pubnub.deleteChannelGroup().apply { + channelGroup = " " + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + + val requests = + findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testMalformedResponse() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(noContent()) + ) + + try { + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn( + aResponse().withBody( + """{"status": 200, "message": "OK", "payload": {}, "service": "ChannelGroups"}""" + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.async { _, status -> + Assertions.assertFalse(status.error) + assertEquals(PNOperationType.PNRemoveGroupOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertTrue(status.affectedChannels == emptyList()) + Assertions.assertTrue(status.affectedChannelGroups == listOf("groupA")) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn( + aResponse().withBody( + """{"status": 200, "message": "OK", "payload": {}, "service": "ChannelGroups"}""" + ) + ) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.async { _, status -> + Assertions.assertFalse(status.error) + assertEquals(PNOperationType.PNRemoveGroupOperation, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_cg", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + Assertions.assertFalse(status.error) + Assertions.assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt new file mode 100644 index 000000000..975d0a604 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt @@ -0,0 +1,118 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import org.awaitility.Awaitility +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class ListAllChannelGroupEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + ) + ) + ) + + val response = pubnub.listAllChannelGroups().sync()!! + assertThat(response.groups, Matchers.contains("a", "b")) + } + + @Test + fun testNullPayload() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"service\": " + + "\"ChannelGroups\"}" + ) + ) + ) + + try { + pubnub.listAllChannelGroups().sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNullBody() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse()) + ) + + try { + pubnub.listAllChannelGroups().sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.listAllChannelGroups().sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.listAllChannelGroups() + .async { _, status -> + Assertions.assertFalse(status.error) + assertEquals(PNOperationType.PNChannelGroupsOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertTrue(status.affectedChannels.isEmpty()) + Assertions.assertTrue(status.affectedChannelGroups.isEmpty()) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt new file mode 100644 index 000000000..6d0cd812e --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt @@ -0,0 +1,132 @@ +package com.pubnub.api.endpoints.channel_groups + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.BaseTest +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class RemoveChannelChannelGroupEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = "groupA" + channels = arrayListOf("ch1", "ch2") + }.sync()!! + } + + @Test + fun testSyncMissingGroup() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + try { + pubnub.removeChannelsFromChannelGroup().apply { + channels = arrayListOf("ch1", "ch2") + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.GROUP_MISSING, e) + } + } + + @Test + fun testSyncMissingChannel() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + try { + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = "groupA" + channels = arrayListOf("ch1", "ch2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}" + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = "groupA" + channels = arrayListOf("ch1", "ch2") + }.async { _, status -> + Assertions.assertFalse(status.error) + assertEquals(PNOperationType.PNRemoveChannelsFromGroupOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertTrue(status.affectedChannels == listOf("ch1", "ch2")) + Assertions.assertTrue(status.affectedChannelGroups == listOf("groupA")) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + +} \ No newline at end of file From a7e4d26845757e1c79465eb98b39ebd5576f75bf Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:28:29 +0200 Subject: [PATCH 20/98] Implement Signals --- .../com/pubnub/api/endpoints/pubsub/Signal.kt | 51 ++++ .../com/pubnub/api/services/SignalService.kt | 19 ++ .../pubnub/api/endpoints/pubsub/SignalTest.kt | 231 ++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/SignalService.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt new file mode 100644 index 000000000..c6f7d1d4e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt @@ -0,0 +1,51 @@ +package com.pubnub.api.endpoints.pubsub + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNPublishResult +import retrofit2.Call +import retrofit2.Response + +class Signal(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { + + lateinit var channel: String + lateinit var message: Any + + private fun isChannelValid() = ::channel.isInitialized + private fun isMessageValid() = ::message.isInitialized + + override fun validateParams() { + super.validateParams() + if (!isChannelValid() || channel.isBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (!isMessageValid()) { + throw PubNubException(PubNubError.MESSAGE_MISSING) + } + } + + override fun getAffectedChannels() = listOf(channel) + + override fun doWork(queryParams: HashMap): Call> { + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.signalService.signal( + pubKey = pubnub.configuration.publishKey, + subKey = pubnub.configuration.subscribeKey, + channel = channel, + message = PubNubUtil.urlEncode(pubnub.mapper.toJson(message)), + options = queryParams + ) + } + + override fun createResponse(input: Response>): PNPublishResult? { + return PNPublishResult( + timetoken = input.body()!![2].toString().toLong() + ) + } + + override fun operationType() = PNOperationType.PNSignalOperation + + override fun isPubKeyRequired() = true + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/SignalService.kt b/src/main/kotlin/com/pubnub/api/services/SignalService.kt new file mode 100644 index 000000000..20ad85ac3 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/SignalService.kt @@ -0,0 +1,19 @@ +package com.pubnub.api.services + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +interface SignalService { + + @GET("/signal/{pubKey}/{subKey}/0/{channel}/0/{payload}") + fun signal( + @Path("pubKey") pubKey: String, + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path(value = "payload", encoded = true) message: String, + @QueryMap(encoded = true) options: Map + ): Call> + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt new file mode 100644 index 000000000..bab3fa741 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt @@ -0,0 +1,231 @@ +package com.pubnub.api.endpoints.pubsub + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.Gson +import com.pubnub.api.* +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult +import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult +import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import okhttp3.HttpUrl +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +class SignalTest : BaseTest() { + + @Test + fun testSignalGetSuccessSync() { + stubFor( + get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn( + aResponse() + .withBody( + """ + [ + 1, + "Sent", + "1000" + ] + """.trimIndent() + ) + ) + ) + + + val payload = mapOf("text" to "hello") + + pubnub.signal().apply { + channel = "coolChannel" + message = payload + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/signal.*"))) + assertEquals(1, requests.size) + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + + val httpUrl = HttpUrl.parse(request.absoluteUrl) + var decodedSignalPayload: String? = null + if (httpUrl != null) { + decodedSignalPayload = httpUrl.pathSegments()[httpUrl.pathSize() - 1] + } + assertEquals(pubnub.mapper.toJson(payload), decodedSignalPayload) + } + + @Test + fun testSignalGetSuccessAsync() { + stubFor( + get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn( + aResponse() + .withBody( + """ + [ + 1, + "Sent", + "1000" + ] + """.trimIndent() + ) + ) + ) + + val payload = UUID.randomUUID().toString() + + val success = AtomicBoolean() + + pubnub.signal().apply { + channel = "coolChannel" + message = payload + }.async { result, status -> + println(status) + result!! + assertFalse(status.error) + assertEquals(PNOperationType.PNSignalOperation, status.operation) + assertEquals("1000", result.timetoken.toString()) + success.set(true) + } + + success.listen() + } + + @Test + fun testSignalSuccessReceive() { + stubFor( + get(urlMatching("/v2/subscribe/mySubscribeKey/coolChannel/0.*")) + .willReturn( + aResponse().withBody( + """ + { + "m": [ + { + "c": "coolChannel", + "f": "0", + "i": "uuid", + "d": "hello", + "e": 1, + "p": { + "t": 1000, + "r": 1 + }, + "k": "mySubscribeKey", + "b": "coolChannel" + } + ], + "t": { + "r": "56", + "t": 1000 + } + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + + + pubnub.addListener(object : SubscribeCallback() { + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { + println("rezz ${Gson().toJson(pnSignalResult)}") + assertEquals("coolChannel", pnSignalResult.channel) + assertEquals("hello", pnSignalResult.message.asString) + assertEquals("uuid", pnSignalResult.publisher) + success.set(true) + } + + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("coolChannel") + }.execute() + + success.listen() + } + + @Test + fun testSignalFailNoChannel() { + try { + pubnub.signal().apply { + message = UUID.randomUUID().toString() + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testSignalFailBlankChannel() { + try { + pubnub.signal().apply { + channel = " " + message = UUID.randomUUID().toString() + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testSignalFailNoMessage() { + try { + pubnub.signal().apply { + channel = UUID.randomUUID().toString() + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.MESSAGE_MISSING, e) + } + } + + @Test + fun testSignalTelemetryParam() { + stubFor( + get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")).willReturn( + aResponse().withBody( + """ + [ + 1, + "Sent", + "1000" + ] + """.trimIndent() + ) + ) + ) + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + pubnub.signal().apply { + channel = "coolChannel" + message = UUID.randomUUID().toString() + }.sync()!! + + pubnub.time() + .sync() + + val requests = findAll(getRequestedFor(urlMatching("/time/0.*"))) + assertEquals(1, requests.size) + val request = requests[0] + assertTrue(request.queryParameter("l_sig").isPresent) + } +} \ No newline at end of file From b4eeffc87482b63579d444f8eec828249a90d7cc Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:29:41 +0200 Subject: [PATCH 21/98] Implement Message Actions --- .../message_actions/AddMessageAction.kt | 61 ++++++++++++++++++ .../message_actions/GetMessageActions.kt | 62 +++++++++++++++++++ .../message_actions/RemoveMessageAction.kt | 52 ++++++++++++++++ .../PNAddMessageActionResult.kt | 3 + .../PNGetMessageActionsResult.kt | 7 +++ .../message_actions/PNMessageAction.kt | 2 +- .../PNRemoveMessageActionResult.kt | 4 ++ .../api/services/MessageActionService.kt | 37 +++++++++++ 8 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/MessageActionService.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt new file mode 100644 index 000000000..85f1470d3 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt @@ -0,0 +1,61 @@ +package com.pubnub.api.endpoints.message_actions + +import com.google.gson.JsonObject +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class AddMessageAction(pubnub: PubNub) : Endpoint, PNAddMessageActionResult>(pubnub) { + + lateinit var channel: String + lateinit var messageAction: PNMessageAction + + override fun validateParams() { + super.validateParams() + if (!::channel.isInitialized || channel.isBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (!::messageAction.isInitialized) { + throw PubNubException(PubNubError.MESSAGE_ACTION_MISSING) + } + if (messageAction.type.isBlank()) { + throw PubNubException(PubNubError.MESSAGE_ACTION_TYPE_MISSING) + } + if (messageAction.value.isBlank()) { + throw PubNubException(PubNubError.MESSAGE_ACTION_VALUE_MISSING) + } + } + + override fun getAffectedChannels() = listOf(channel) + + override fun doWork(queryParams: HashMap): Call> { + queryParams.putAll(encodeParams(queryParams)) + + val body = JsonObject() + body.addProperty("type", messageAction.type) + body.addProperty("value", messageAction.value) + + return pubnub.retrofitManager.messageActionService + .addMessageAction( + subKey = pubnub.configuration.subscribeKey, + channel = channel, + messageTimetoken = messageAction.messageTimetoken.toString().toLowerCase(), + body = body, + options = queryParams + ) + } + + override fun createResponse(input: Response>): PNAddMessageActionResult? { + return PNAddMessageActionResult(input.body()!!.data!!) + } + + override fun operationType() = PNOperationType.PNAddMessageAction +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt new file mode 100644 index 000000000..3b8548011 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt @@ -0,0 +1,62 @@ +package com.pubnub.api.endpoints.message_actions + +import com.google.gson.JsonObject +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class GetMessageActions(pubnub: PubNub) : Endpoint(pubnub) { + + lateinit var channel: String + var start: Long? = null + var end: Long? = null + var limit: Int? = null + + override fun validateParams() { + super.validateParams() + if (!::channel.isInitialized || channel.isBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + } + + override fun getAffectedChannels() = listOf(channel) + + override fun getAffectedChannelGroups() = emptyList() + + override fun doWork(queryParams: HashMap): Call { + + start?.let { queryParams["start"] = it.toString().toLowerCase() } + end?.let { queryParams["end"] = it.toString().toLowerCase() } + limit?.let { queryParams["limit"] = it.toString().toLowerCase() } + + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.messageActionService + .getMessageActions( + pubnub.configuration.subscribeKey, + channel, + queryParams + ) + } + + override fun createResponse(input: Response): PNGetMessageActionsResult? { + val iterator = pubnub.mapper.getArrayIterator(input.body()!!, "data") + val list = mutableListOf() + while (iterator.hasNext()) { + val messageActionJson = iterator.next() + list.add(pubnub.mapper.convertValue(messageActionJson, PNMessageAction::class.java)) + } + return PNGetMessageActionsResult().apply { + actions = list + } + } + + override fun operationType() = PNOperationType.PNGetMessageActions +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt new file mode 100644 index 000000000..970628b13 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt @@ -0,0 +1,52 @@ +package com.pubnub.api.endpoints.message_actions + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class RemoveMessageAction(pubnub: PubNub) : Endpoint(pubnub) { + + lateinit var channel: String + var messageTimetoken: Long? = null + var actionTimetoken: Long? = null + + override fun validateParams() { + super.validateParams() + if (!::channel.isInitialized || channel.isBlank()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (messageTimetoken == null) { + throw PubNubException(PubNubError.MESSAGE_TIMETOKEN_MISSING) + } + if (actionTimetoken == null) { + throw PubNubException(PubNubError.MESSAGE_ACTION_TIMETOKEN_MISSING) + } + } + + override fun getAffectedChannels() = listOf(channel) + + override fun doWork(queryParams: HashMap): Call { + queryParams.putAll(encodeParams(queryParams)) + + return pubnub.retrofitManager.messageActionService + .deleteMessageAction( + subKey = pubnub.configuration.subscribeKey, + channel = channel, + messageTimetoken = messageTimetoken.toString().toLowerCase(), + actionTimetoken = actionTimetoken.toString().toLowerCase(), + options = queryParams + ) + } + + override fun createResponse(input: Response): PNRemoveMessageActionResult? { + return PNRemoveMessageActionResult() + } + + override fun operationType() = PNOperationType.PNAddMessageAction +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.kt new file mode 100644 index 000000000..4d22e88d7 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.kt @@ -0,0 +1,3 @@ +package com.pubnub.api.models.consumer.message_actions + +class PNAddMessageActionResult(pnMessageAction: PNMessageAction) : PNMessageAction(pnMessageAction) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt new file mode 100644 index 000000000..905394f69 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.message_actions + +class PNGetMessageActionsResult { + + var actions: List = emptyList() + internal set +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt index 2f06b46e2..4b80d022c 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt @@ -1,6 +1,6 @@ package com.pubnub.api.models.consumer.message_actions -class PNMessageAction { +open class PNMessageAction { val type: String val value: String val messageTimetoken: Long diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.kt new file mode 100644 index 000000000..e8f88223a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.kt @@ -0,0 +1,4 @@ +package com.pubnub.api.models.consumer.message_actions + +class PNRemoveMessageActionResult { +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt new file mode 100644 index 000000000..7acb13ee5 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt @@ -0,0 +1,37 @@ +package com.pubnub.api.services + +import com.google.gson.JsonObject +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.http.* + +interface MessageActionService { + + @POST("v1/message-actions/{subKey}/channel/{channel}/message/{messageTimetoken}") + @Headers("Content-Type: application/json; charset=UTF-8") + fun addMessageAction( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path("messageTimetoken") messageTimetoken: String, + @Body body: Any, + @QueryMap(encoded = true) options: Map + ): Call> + + @GET("v1/message-actions/{subKey}/channel/{channel}") + fun getMessageActions( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap(encoded = true) options: Map + ): Call + + @DELETE("v1/message-actions/{subKey}/channel/{channel}/message/{messageTimetoken}/action/{actionTimetoken}") + fun deleteMessageAction( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @Path("messageTimetoken") messageTimetoken: String, + @Path("actionTimetoken") actionTimetoken: String, + @QueryMap(encoded = true) options: Map + ): Call + +} \ No newline at end of file From 2e1acc29e1545bfb3bdabd49b4c55a45be552412 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:30:32 +0200 Subject: [PATCH 22/98] Implement Push Notification APIs --- .../api/endpoints/push/AddChannelsToPush.kt | 72 +++ .../api/endpoints/push/ListPushProvisions.kt | 68 +++ .../push/RemoveAllPushChannelsForDevice.kt | 66 +++ .../endpoints/push/RemoveChannelsFromPush.kt | 71 +++ .../com/pubnub/api/enums/PNPushEnvironment.kt | 6 + .../kotlin/com/pubnub/api/enums/PNPushType.kt | 17 + .../api/models/consumer/push/PNPushResults.kt | 12 + .../push/payload/PushPayloadHelper.kt | 171 ++++++ .../push/payload/PushPayloadSerializer.kt | 7 + .../com/pubnub/api/services/PushService.kt | 54 ++ .../endpoints/push/AddChannelsToPushTest.kt | 227 ++++++++ .../endpoints/push/ListPushProvisionsTest.kt | 320 +++++++++++ .../push/PushPayloadHelperHelperTest.kt | 495 ++++++++++++++++++ .../RemoveAllPushChannelsForDeviceTest.kt | 183 +++++++ .../push/RemoveChannelsFromPushTest.kt | 228 ++++++++ 15 files changed, 1997 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNPushEnvironment.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNPushType.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/PushService.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt new file mode 100644 index 000000000..59c23945e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt @@ -0,0 +1,72 @@ +package com.pubnub.api.endpoints.push + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushAddChannelResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class AddChannelsToPush(pubnub: PubNub) : Endpoint, PNPushAddChannelResult>(pubnub) { + + lateinit var pushType: PNPushType + lateinit var channels: List + lateinit var deviceId: String + var environment = PNPushEnvironment.DEVELOPMENT + lateinit var topic: String + + override fun getAffectedChannels() = channels + + override fun validateParams() { + super.validateParams() + if (!::pushType.isInitialized) { + throw PubNubException(PubNubError.PUSH_TYPE_MISSING) + } + if (!::deviceId.isInitialized || deviceId.isBlank()) { + throw PubNubException(PubNubError.DEVICE_ID_MISSING) + } + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (pushType == PNPushType.APNS2) { + if (!::topic.isInitialized || topic.isBlank()) { + throw PubNubException(PubNubError.PUSH_TOPIC_MISSING) + } + } + } + + override fun doWork(queryParams: HashMap): Call> { + queryParams["add"] = channels.toCsv() + + if (pushType != PNPushType.APNS2) { + queryParams["type"] = pushType.toParamString() + + return pubnub.retrofitManager.pushService + .modifyChannelsForDevice( + subKey = pubnub.configuration.subscribeKey, + pushToken = deviceId, + options = queryParams + ) + } + + queryParams["environment"] = environment.name.toLowerCase() + queryParams["topic"] = topic + + return pubnub.retrofitManager.pushService + .modifyChannelsForDeviceApns2( + subKey = pubnub.configuration.subscribeKey, + deviceApns2 = deviceId, + options = queryParams + ) + + } + + // is there any usable data in the response? + override fun createResponse(input: Response>): PNPushAddChannelResult? { + return PNPushAddChannelResult() + } + + override fun operationType() = PNOperationType.PNAddPushNotificationsOnChannelsOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt new file mode 100644 index 000000000..9607c2117 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt @@ -0,0 +1,68 @@ +package com.pubnub.api.endpoints.push + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class ListPushProvisions(pubnub: PubNub) : Endpoint, PNPushListProvisionsResult>(pubnub) { + + lateinit var pushType: PNPushType + lateinit var deviceId: String + var environment = PNPushEnvironment.DEVELOPMENT + lateinit var topic: String + + override fun validateParams() { + super.validateParams() + if (!::pushType.isInitialized) { + throw PubNubException(PubNubError.PUSH_TYPE_MISSING) + } + if (!::deviceId.isInitialized || deviceId.isBlank()) { + throw PubNubException(PubNubError.DEVICE_ID_MISSING) + } + if (pushType == PNPushType.APNS2) { + if (!::topic.isInitialized || topic.isBlank()) { + throw PubNubException(PubNubError.PUSH_TOPIC_MISSING) + } + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (pushType != PNPushType.APNS2) { + queryParams["type"] = pushType.toParamString() + + return pubnub.retrofitManager.pushService + .listChannelsForDevice( + subKey = pubnub.configuration.subscribeKey, + pushToken = deviceId, + options = queryParams + ) + } + + queryParams["environment"] = environment.name.toLowerCase() + queryParams["topic"] = topic + + return pubnub.retrofitManager.pushService + .listChannelsForDeviceApns2( + subKey = pubnub.configuration.subscribeKey, + deviceApns2 = deviceId, + options = queryParams + ) + + } + + override fun createResponse(input: Response>): PNPushListProvisionsResult? { + return PNPushListProvisionsResult().apply { + channels = input.body()!! + } + } + + override fun operationType() = PNOperationType.PNPushNotificationEnabledChannelsOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt new file mode 100644 index 000000000..952ad080f --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt @@ -0,0 +1,66 @@ +package com.pubnub.api.endpoints.push + +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveAllChannelsResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class RemoveAllPushChannelsForDevice(pubnub: PubNub) : Endpoint, PNPushRemoveAllChannelsResult>(pubnub) { + + lateinit var pushType: PNPushType + lateinit var deviceId: String + var environment = PNPushEnvironment.DEVELOPMENT + lateinit var topic: String + + override fun validateParams() { + super.validateParams() + if (!::pushType.isInitialized) { + throw PubNubException(PubNubError.PUSH_TYPE_MISSING) + } + if (!::deviceId.isInitialized || deviceId.isBlank()) { + throw PubNubException(PubNubError.DEVICE_ID_MISSING) + } + if (pushType == PNPushType.APNS2) { + if (!::topic.isInitialized || topic.isBlank()) { + throw PubNubException(PubNubError.PUSH_TOPIC_MISSING) + } + } + } + + override fun doWork(queryParams: HashMap): Call> { + if (pushType != PNPushType.APNS2) { + queryParams["type"] = pushType.toParamString() + + return pubnub.retrofitManager.pushService + .removeAllChannelsForDevice( + subKey = pubnub.configuration.subscribeKey, + pushToken = deviceId, + options = queryParams + ) + } + + queryParams["environment"] = environment.name.toLowerCase() + queryParams["topic"] = topic + + return pubnub.retrofitManager.pushService + .removeAllChannelsForDeviceApns2( + subKey = pubnub.configuration.subscribeKey, + deviceApns2 = deviceId, + options = queryParams + ) + + } + + override fun createResponse(input: Response>): PNPushRemoveAllChannelsResult? { + return PNPushRemoveAllChannelsResult() + } + + override fun operationType() = PNOperationType.PNRemoveAllPushNotificationsOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt new file mode 100644 index 000000000..15bc20fb8 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt @@ -0,0 +1,71 @@ +package com.pubnub.api.endpoints.push + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class RemoveChannelsFromPush(pubnub: PubNub) : Endpoint, PNPushRemoveChannelResult>(pubnub) { + + lateinit var pushType: PNPushType + lateinit var channels: List + lateinit var deviceId: String + var environment = PNPushEnvironment.DEVELOPMENT + lateinit var topic: String + + override fun getAffectedChannels() = channels + + override fun validateParams() { + super.validateParams() + if (!::pushType.isInitialized) { + throw PubNubException(PubNubError.PUSH_TYPE_MISSING) + } + if (!::deviceId.isInitialized || deviceId.isBlank()) { + throw PubNubException(PubNubError.DEVICE_ID_MISSING) + } + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (pushType == PNPushType.APNS2) { + if (!::topic.isInitialized || topic.isBlank()) { + throw PubNubException(PubNubError.PUSH_TOPIC_MISSING) + } + } + } + + override fun doWork(queryParams: HashMap): Call> { + queryParams["remove"] = channels.toCsv() + + if (pushType != PNPushType.APNS2) { + queryParams["type"] = pushType.toParamString() + + return pubnub.retrofitManager.pushService + .modifyChannelsForDevice( + subKey = pubnub.configuration.subscribeKey, + pushToken = deviceId, + options = queryParams + ) + } + + queryParams["environment"] = environment.name.toLowerCase() + queryParams["topic"] = topic + + return pubnub.retrofitManager.pushService + .modifyChannelsForDeviceApns2( + subKey = pubnub.configuration.subscribeKey, + deviceApns2 = deviceId, + options = queryParams + ) + + } + + override fun createResponse(input: Response>): PNPushRemoveChannelResult? { + return PNPushRemoveChannelResult() + } + + override fun operationType() = PNOperationType.PNRemovePushNotificationsFromChannelsOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNPushEnvironment.kt b/src/main/kotlin/com/pubnub/api/enums/PNPushEnvironment.kt new file mode 100644 index 000000000..1a14b6a80 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNPushEnvironment.kt @@ -0,0 +1,6 @@ +package com.pubnub.api.enums + +enum class PNPushEnvironment { + DEVELOPMENT, + PRODUCTION +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/enums/PNPushType.kt b/src/main/kotlin/com/pubnub/api/enums/PNPushType.kt new file mode 100644 index 000000000..73429ccf3 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNPushType.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.enums + +import java.util.* + +enum class PNPushType(s: String) { + + APNS("apns"), + MPNS("mpns"), + FCM("gcm"), + APNS2("apns2"); + + private val value: String = s + + fun toParamString(): String { + return value.toLowerCase(Locale.US) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt new file mode 100644 index 000000000..6c1baf1b2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer.push + +class PNPushAddChannelResult + +class PNPushListProvisionsResult { + var channels = listOf() + internal set +} + +class PNPushRemoveAllChannelsResult + +class PNPushRemoveChannelResult \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.kt b/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.kt new file mode 100644 index 000000000..6773abf7d --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.kt @@ -0,0 +1,171 @@ +package com.pubnub.api.models.consumer.push.payload + +import com.pubnub.api.enums.PNPushEnvironment + +class PushPayloadHelper { + + var commonPayload: Map? = null + var fcmPayload: FCMPayload? = null + var mpnsPayload: MPNSPayload? = null + var apnsPayload: APNSPayload? = null + + fun build(): Map { + return mutableMapOf().apply { + apnsPayload?.let { + it.toMap().run { + if (isNotEmpty()) { + put("pn_apns", this) + } + } + } + fcmPayload?.let { + it.toMap().run { + if (isNotEmpty()) { + put("pn_gcm", this) + } + } + } + mpnsPayload?.let { + it.toMap().run { + if (isNotEmpty()) { + put("pn_mpns", this) + } + } + } + commonPayload?.let { putAll(it) } + } + } + + + class APNSPayload : PushPayloadSerializer { + var aps: APS? = null + var apns2Configurations: List? = null + var custom: Map? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + aps?.let { + it.toMap().run { + if (this.isNotEmpty()) { + put("aps", this) + } + } + } + apns2Configurations?.let { put("pn_push", it.map { it.toMap() }.filter { it.isNotEmpty() }.toList()) } + custom?.let { putAll(it) } + } + } + + class APS : PushPayloadSerializer { + var alert: Any? = null + var badge: Int? = null + var sound: String? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + alert?.let { put("alert", it) } + badge?.let { put("badge", it) } + sound?.let { put("sound", it) } + } + } + + } + + class APNS2Configuration : PushPayloadSerializer { + var collapseId: String? = null + var expiration: String? = null + var targets: List? = null + var version: String? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + collapseId?.let { put("collapse_id", it) } + expiration?.let { put("expiration", it) } + targets?.let { + it.map { it.toMap() }.filter { it.isNotEmpty() }.toList().run { + if (this.isNotEmpty()) { + put("targets", this) + } + } + } + version?.let { put("version", it) } + } + } + + class Target : PushPayloadSerializer { + var topic: String? = null + var excludeDevices: List? = null + var environment: PNPushEnvironment? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + topic?.let { put("topic", it) } + excludeDevices?.let { put("excluded_devices", it) } + environment?.let { put("environment", it.name.toLowerCase()) } + } + } + } + + } + + } + + class MPNSPayload : PushPayloadSerializer { + var count: Int? = null + var backTitle: String? = null + var title: String? = null + var backContent: String? = null + var type: String? = null + var custom: Map? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + count?.let { put("count", it) } + backTitle?.let { put("back_title", it) } + title?.let { put("title", it) } + backContent?.let { put("back_content", it) } + type?.let { put("type", it) } + custom?.let { putAll(it) } + } + } + + } + + class FCMPayload : PushPayloadSerializer { + var custom: Map? = null + var data: Map? = null + var notification: Notification? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + custom?.let { putAll(it) } + data?.let { + if (it.isNotEmpty()) { + put("data", it) + } + } + notification?.let { + it.toMap().run { + if (this.isNotEmpty()) { + put("notification", this) + } + } + } + } + } + + class Notification : PushPayloadSerializer { + var title: String? = null + var body: String? = null + var image: String? = null + + override fun toMap(): Map { + return mutableMapOf().apply { + title?.let { put("title", it) } + body?.let { put("body", it) } + image?.let { put("image", it) } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.kt b/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.kt new file mode 100644 index 000000000..0551378f5 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.push.payload + +interface PushPayloadSerializer { + + fun toMap(): Map + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PushService.kt b/src/main/kotlin/com/pubnub/api/services/PushService.kt new file mode 100644 index 000000000..33682b2e0 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/PushService.kt @@ -0,0 +1,54 @@ +package com.pubnub.api.services + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap + +interface PushService { + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}") + fun modifyChannelsForDevice( + @Path("subKey") subKey: String, + @Path("pushToken") pushToken: String, + @QueryMap options: Map + ): Call> + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}/remove") + fun removeAllChannelsForDevice( + @Path("subKey") subKey: String, + @Path("pushToken") pushToken: String, + @QueryMap options: Map + ): Call> + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}") + fun listChannelsForDevice( + @Path("subKey") subKey: String, + @Path("pushToken") pushToken: String, + @QueryMap options: Map + ): Call> + + // V2 (APNS2) + + // V2 (APNS2) + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}") + fun modifyChannelsForDeviceApns2( + @Path("subKey") subKey: String, + @Path("deviceApns2") deviceApns2: String, + @QueryMap options: Map + ): Call> + + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}") + fun listChannelsForDeviceApns2( + @Path("subKey") subKey: String, + @Path("deviceApns2") deviceApns2: String, + @QueryMap options: Map + ): Call> + + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}/remove") + fun removeAllChannelsForDeviceApns2( + @Path("subKey") subKey: String, + @Path("deviceApns2") deviceApns2: String, + @QueryMap options: Map + ): Call> +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt new file mode 100644 index 000000000..2261f2684 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt @@ -0,0 +1,227 @@ +package com.pubnub.api.endpoints.push + +import com.github.tomakehurst.wiremock.client.WireMock +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.enums.PNStatusCategory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class AddChannelsToPushTest : BaseTest() { + + @Test + fun testAddAppleSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("ch1,ch2,ch3", requests[0].queryParameter("add").firstValue()) + Assertions.assertEquals("apns", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testAddFirebaseSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("ch1,ch2,ch3", requests[0].queryParameter("add").firstValue()) + Assertions.assertEquals("gcm", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testAddMicrosoftSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.MPNS + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("ch1,ch2,ch3", requests[0].queryParameter("add").firstValue()) + Assertions.assertEquals("mpns", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testAddApns2SuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + channels = listOf("ch1", "ch2", "ch3") + topic = "topic" + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("ch1,ch2,ch3", requests[0].queryParameter("add").firstValue()) + Assertions.assertEquals("development", requests[0].queryParameter("environment").firstValue()) + Assertions.assertEquals("topic", requests[0].queryParameter("topic").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("type").isPresent) + } + + @Test + fun testIsAuthRequiredSuccessAdd() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAdd() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + val success = AtomicBoolean() + + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + }.async { result, status -> + Assertions.assertFalse(status.error) + Assertions.assertEquals(PNOperationType.PNAddPushNotificationsOnChannelsOperation, status.operation) + Assertions.assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertEquals(status.affectedChannels, listOf("ch1", "ch2", "ch3")) + Assertions.assertTrue(status.affectedChannelGroups.isEmpty()) + success.set(true) + } + + success.listen() + } + + + @Test + fun testEmptySubscribeKeyAdd() { + pubnub.configuration.subscribeKey = "" + + try { + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testNullPushTypeAdd() { + try { + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PUSH_TYPE_MISSING, e) + } + } + + @Test + fun testNullDeviceIdAdd() { + try { + pubnub.addPushNotificationsOnChannels().apply { + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testEmptyDeviceIdAdd() { + try { + pubnub.addPushNotificationsOnChannels().apply { + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2", "ch3") + deviceId = " " + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testMissingChannelsAdd() { + try { + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testEmptyChannelsAdd() { + try { + pubnub.addPushNotificationsOnChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = emptyList() + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt new file mode 100644 index 000000000..ab1fa4ff4 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt @@ -0,0 +1,320 @@ +package com.pubnub.api.endpoints.push + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.enums.PNStatusCategory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +class ListPushProvisionsTest : BaseTest() { + + @Test + fun testAppleSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + topic = "irrelevant" + }.sync()!! + + assertEquals(listOf("ch1", "ch2", "ch3"), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("apns", requests[0].queryParameter("type").firstValue()) + assertFalse(requests[0].queryParameter("environment").isPresent) + assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testFirebaseSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + topic = "irrelevant" + }.sync()!! + + assertEquals(listOf("ch1", "ch2", "ch3"), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("gcm", requests[0].queryParameter("type").firstValue()) + assertFalse(requests[0].queryParameter("environment").isPresent) + assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testMicrosoftSuccessSync() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.MPNS + topic = "irrelevant" + }.sync()!! + + assertEquals(listOf("ch1", "ch2", "ch3"), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("mpns", requests[0].queryParameter("type").firstValue()) + assertFalse(requests[0].queryParameter("environment").isPresent) + assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testApns2SuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + topic = "news" + }.sync()!! + + assertEquals(listOf("ch1", "ch2", "ch3"), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("development", requests[0].queryParameter("environment").firstValue()) + assertEquals("news", requests[0].queryParameter("topic").firstValue()) + } + + @Test + fun testApns2SuccessSync_Environment() { + stubFor( + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + topic = "news" + environment = PNPushEnvironment.PRODUCTION + }.sync()!! + + assertEquals(listOf("ch1", "ch2", "ch3"), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("production", requests[0].queryParameter("environment").firstValue()) + assertEquals("news", requests[0].queryParameter("topic").firstValue()) + } + + @Test + fun testEmptyArray() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[]")) + ) + + val response = pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync()!! + + assertEquals(emptyList(), response.channels) + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun testEmptyBody() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("")) + ) + + try { + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync()!! + failTest() + } catch (e: Exception) { + e.printStackTrace() + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testMalformedResponseSync() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(ok().withBody("{")) + ) + + try { + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testMalformedResponseAsync() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(ok().withBody("{")) + ) + + val success = AtomicBoolean() + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.async { _, status -> + println(status) + assertPnException(PubNubError.PARSING_ERROR, status) + assertEquals(PNStatusCategory.PNMalformedResponseCategory, status.category) + success.set(true) + } + success.listen() + } + + + @Test + fun testIsAuthRequiredSuccess() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccess() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + val success = AtomicBoolean() + + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + }.async { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNPushNotificationEnabledChannelsOperation, status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertTrue(status.affectedChannels.isEmpty()) + Assertions.assertTrue(status.affectedChannelGroups.isEmpty()) + success.set(true) + } + + success.listen() + } + + @Test + fun testNullSubscribeKey() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + pubnub.configuration.subscribeKey = " " + + try { + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + }.sync()!! + failTest("Didn't throw SUBSCRIBE_KEY_MISSING") + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testValidationNoDeviceId() { + try { + pubnub.auditPushChannelProvisions().apply { + pushType = PNPushType.FCM + }.sync()!! + failTest("Should throw no device id") + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testValidationNoPushTYpe() { + try { + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + }.sync()!! + failTest("Should throw no push type") + } catch (e: PubNubException) { + assertPnException(PubNubError.PUSH_TYPE_MISSING, e) + } + } + + @Test + fun testValidationNoTopic() { + try { + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + }.sync()!! + failTest("Should throw no topic") + } catch (e: PubNubException) { + assertPnException(PubNubError.PUSH_TOPIC_MISSING, e) + } + } + + @Test + fun testValidationDefaultEnvironment() { + stubFor( + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("""["ch1", "ch2", "ch3"]""")) + ) + + pubnub.auditPushChannelProvisions().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + topic = UUID.randomUUID().toString() + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("development", requests[0].queryParameter("environment").firstValue()) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt new file mode 100644 index 000000000..0a120500b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt @@ -0,0 +1,495 @@ +package com.pubnub.api.endpoints.push + +import com.google.gson.Gson +import com.pubnub.api.BaseTest +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper +import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.* +import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.APNSPayload.APNS2Configuration +import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.APNSPayload.APS +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* + +class PushPayloadHelperHelperTest : BaseTest() { + + @Test + fun testPayloads_Missing() { + val pushPayloadHelper = PushPayloadHelper() + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testPayloads_Null() { + val pushPayloadHelper = PushPayloadHelper() + pushPayloadHelper.apnsPayload = null + pushPayloadHelper.commonPayload = null + pushPayloadHelper.fcmPayload = null + pushPayloadHelper.mpnsPayload = null + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testPayloads_Empty() { + val pushPayloadHelper = PushPayloadHelper() + pushPayloadHelper.apnsPayload = APNSPayload() + pushPayloadHelper.commonPayload = HashMap() + pushPayloadHelper.fcmPayload = FCMPayload() + pushPayloadHelper.mpnsPayload = MPNSPayload() + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testApple_Empty() { + val pushPayloadHelper = PushPayloadHelper() + val apnsPayload = APNSPayload() + apnsPayload.aps = APS() + apnsPayload.apns2Configurations = ArrayList() + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testApple_Valid() { + val pushPayloadHelper = PushPayloadHelper() + + val apnsPayload = APNSPayload() + + val aps = APS() + aps.alert = "alert" + aps.badge = 5 + + apnsPayload.aps = aps + apnsPayload.apns2Configurations = emptyList() + apnsPayload.custom = hashMapOf( + "key_1" to "1", + "key_2" to 2 + ) + pushPayloadHelper.apnsPayload = apnsPayload + + val map = pushPayloadHelper.build() + + val pnApnsDataMap = map["pn_apns"] as Map<*, *> + assertEquals("1", pnApnsDataMap["key_1"]) + assertEquals(2, pnApnsDataMap["key_2"]) + + val apsMap = pnApnsDataMap["aps"] as Map<*, *> + assertEquals("alert", apsMap["alert"]) + assertEquals(5, apsMap["badge"]) + + val pushList = pnApnsDataMap["pn_push"] as List<*> + assertEquals(0, pushList.size) + } + + @Test + fun testApple_Aps() { + val pushPayloadHelper = PushPayloadHelper() + val apnsPayload = APNSPayload() + + apnsPayload.aps = APS().apply { + alert = "alert" + badge = 5 + } + pushPayloadHelper.apnsPayload = apnsPayload + + apnsPayload.custom = mapOf("key_2" to "2") + + val map = pushPayloadHelper.build() + + val pnApnsDataMap = map["pn_apns"] as Map<*, *> + val apsMap = pnApnsDataMap["aps"] as Map<*, *> + + assertEquals("alert", apsMap["alert"]) + assertEquals(5, apsMap["badge"]) + assertFalse(apsMap.containsKey("sound")) + assertFalse(pnApnsDataMap.containsKey("pn_push")) + assertFalse(pnApnsDataMap.containsKey("key_1")) + assertEquals("2", pnApnsDataMap["key_2"]) + } + + @Test + fun testApple_PnPushArray() { + val pushPayloadHelper = PushPayloadHelper() + + val target1 = APNS2Configuration.Target().apply { + environment = PNPushEnvironment.DEVELOPMENT + topic = "topic_1" + } + val target2 = APNS2Configuration.Target().apply { + environment = PNPushEnvironment.PRODUCTION + } + val target3 = APNS2Configuration.Target().apply { + environment = PNPushEnvironment.PRODUCTION + topic = "topic_3" + excludeDevices = listOf("ex_1", "ex_2") + } + val target4 = APNS2Configuration.Target().apply { + environment = null + topic = null + excludeDevices = null + } + val target5 = APNS2Configuration.Target().apply { + environment = PNPushEnvironment.PRODUCTION + topic = "topic_5" + excludeDevices = listOf() + } + val target6 = APNS2Configuration.Target().apply { + topic = "topic_6" + excludeDevices = null + } + val target7 = APNS2Configuration.Target() + + val apns2Config1 = APNS2Configuration().apply { + collapseId = "collapse_1" + expiration = "exp_1" + version = "v1" + targets = null + } + val apns2Config2 = APNS2Configuration().apply { + collapseId = "collapse_2" + expiration = "exp_2" + version = "v2" + targets = emptyList() + } + val apns2Config3 = APNS2Configuration().apply { + collapseId = null + expiration = "" + version = "v3" + targets = listOf(target1, target2, target3, target4, target5, target6, target7) + } + val apns2Config4 = APNS2Configuration().apply { + collapseId = null + expiration = null + version = null + } + val apns2Config5 = APNS2Configuration() + + pushPayloadHelper.apnsPayload = APNSPayload().apply { + apns2Configurations = listOf( + apns2Config1, apns2Config2, apns2Config3, apns2Config4, apns2Config5 + ) + } + + val map = pushPayloadHelper.build() + + println(Gson().toJson(map)) + + val apnsMap = map["pn_apns"] as Map<*, *> + val pnPushList = apnsMap["pn_push"] as List> + + assertEquals(3, pnPushList.size) + + assertEquals("exp_1", pnPushList[0]["expiration"]) + assertEquals("collapse_1", pnPushList[0]["collapse_id"]) + assertEquals("v1", pnPushList[0]["version"]) + assertFalse(pnPushList[0].containsKey("targets")) + + assertEquals("exp_2", pnPushList[1]["expiration"]) + assertEquals("collapse_2", pnPushList[1]["collapse_id"]) + assertEquals("v2", pnPushList[1]["version"]) + assertFalse(pnPushList[1].containsKey("targets")) + + assertEquals("", pnPushList[2]["expiration"]) + assertFalse(pnPushList[2].containsKey("collapse_id")) + assertEquals("v3", pnPushList[2]["version"]) + assertTrue(pnPushList[2].containsKey("targets")) + + + val pnTargetsMap = pnPushList[2]["targets"] as List> + + assertEquals("development", pnTargetsMap[0]["environment"]) + assertEquals("topic_1", pnTargetsMap[0]["topic"]) + assertFalse(pnTargetsMap[0].containsKey("excludeDevices")) + + assertEquals("production", pnTargetsMap[1]["environment"]) + assertFalse(pnTargetsMap[1].containsKey("topic_2")) + assertFalse(pnTargetsMap[1].containsKey("excludeDevices")) + + assertEquals("production", pnTargetsMap[2]["environment"]) + assertEquals("topic_3", pnTargetsMap[2]["topic"]) + assertEquals("ex_1", (pnTargetsMap[2]["excluded_devices"] as List<*>?)!![0]) + assertEquals("ex_2", (pnTargetsMap[2]["excluded_devices"] as List<*>?)!![1]) + + assertEquals("production", pnTargetsMap[3]["environment"]) + assertFalse(pnTargetsMap[3].containsKey("topic_5")) + assertFalse(pnTargetsMap[3].containsKey("excludeDevices")) + + assertFalse(pnTargetsMap[4].containsKey("environment")) + assertEquals("topic_6", pnTargetsMap[4]["topic"]) + assertFalse(pnTargetsMap[4].containsKey("environment")) + } + + @Test + fun testApple_Aps_Empty() { + val pushPayloadHelper = PushPayloadHelper() + + val apnsPayload = APNSPayload() + apnsPayload.aps = APS() + pushPayloadHelper.apnsPayload = apnsPayload + + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testCommonPayload_Valid() { + val map = PushPayloadHelper().apply { + commonPayload = + mapOf( + "common_key_1" to 1, + "common_key_2" to "2", + "common_key_3" to true + ) + }.build() + + assertEquals(map["common_key_1"], 1) + assertEquals(map["common_key_2"], "2") + assertEquals(map["common_key_3"], true) + } + + @Test + fun testCommonPayload_Invalid() { + val pushPayloadHelper = PushPayloadHelper() + + pushPayloadHelper.commonPayload = mapOf() + + val map = pushPayloadHelper.build() + assertTrue(map.isEmpty()) + } + + @Test + fun testGoogle_Valid_1() { + val pushPayloadHelper = PushPayloadHelper() + + pushPayloadHelper.fcmPayload = FCMPayload().apply { + notification = FCMPayload.Notification().apply { + body = "Notification body" + image = null + title = "" + } + custom = mapOf("a" to "a", "b" to 1) + data = mapOf("data_1" to "a", "data_2" to 1) + } + + val map = pushPayloadHelper.build() + + print(map) + + val pnFcmMap = map["pn_gcm"] as Map<*, *> + assertNotNull(pnFcmMap) + + val pnFcmDataMap = pnFcmMap["data"] as Map<*, *> + val pnFcmNotificationsMap = pnFcmMap["notification"] as Map<*, *> + + assertNotNull(pnFcmDataMap) + assertNotNull(pnFcmNotificationsMap) + + assertEquals(pnFcmMap["a"], "a") + assertEquals(pnFcmMap["b"], 1) + assertEquals(pnFcmDataMap["data_1"], "a") + assertEquals(pnFcmDataMap["data_2"], 1) + assertEquals(pnFcmNotificationsMap["body"], "Notification body") + assertEquals(pnFcmNotificationsMap["image"], null) + assertEquals(pnFcmNotificationsMap["title"], "") + } + + @Test + fun testGoogle_Empty() { + val pushPayloadHelper = PushPayloadHelper() + val fcmPayload = FCMPayload() + pushPayloadHelper.fcmPayload = fcmPayload + val map = pushPayloadHelper.build() + val pnFcmMap = map["pn_gcm"] + assertNull(pnFcmMap) + } + + @Test + fun testGoogle_EmptyNotification() { + val pushPayloadHelper = PushPayloadHelper() + val notification = FCMPayload.Notification() + val customMap = HashMap() + customMap["key_1"] = "1" + customMap["key_2"] = 2 + val fcmPayload = FCMPayload() + fcmPayload.notification = notification + fcmPayload.custom = customMap + pushPayloadHelper.fcmPayload = fcmPayload + val map = pushPayloadHelper.build() + val pnFcmMap = map["pn_gcm"] as Map<*, *> + val pnFcmNotificationMap = pnFcmMap["notification"] + assertNull(pnFcmNotificationMap) + } + + @Test + fun testGoogle_EmptyData() { + val pushPayloadHelper = PushPayloadHelper() + val fcmPayload = FCMPayload() + val dataMap = HashMap() + fcmPayload.data = dataMap + pushPayloadHelper.fcmPayload = fcmPayload + val map = pushPayloadHelper.build() + val pnFcmMap = map["pn_gcm"] + assertNull(pnFcmMap) + } + + @Test + fun testGoogle_Valid_2() { + val pushPayloadHelper = PushPayloadHelper() + val fcmPayload = FCMPayload().apply { + data = mapOf( + "key_1" to "value_1", + "key_2" to 2, + "key_3" to true, + "key_4" to "" + ) + } + pushPayloadHelper.fcmPayload = fcmPayload + val map = pushPayloadHelper.build() + + val pnFcmMap = map["pn_gcm"] as Map<*, *> + val pnFcmDataMap = pnFcmMap["data"] as Map<*, *> + assertNotNull(pnFcmDataMap) + assertFalse(pnFcmDataMap.isEmpty()) + assertTrue(pnFcmDataMap.containsKey("key_1")) + assertTrue(pnFcmDataMap.containsKey("key_2")) + assertTrue(pnFcmDataMap.containsKey("key_3")) + assertTrue(pnFcmDataMap.containsKey("key_4")) + assertNotNull(pnFcmDataMap["key_1"]) + assertNotNull(pnFcmDataMap["key_2"]) + assertNotNull(pnFcmDataMap["key_3"]) + assertNotNull(pnFcmDataMap["key_4"]) + } + + @Test + fun testGoogle_Custom() { + val pushPayloadHelper = PushPayloadHelper() + pushPayloadHelper.fcmPayload = FCMPayload().apply { + custom = mapOf( + "key_1" to "value_1", + "key_2" to 2, + "key_3" to true, + "key_4" to "" + ) + } + val map = pushPayloadHelper.build() + + val pnFcmMap = map["pn_gcm"] as Map<*, *> + assertNotNull(pnFcmMap) + assertFalse(pnFcmMap.isEmpty()) + assertTrue(pnFcmMap.containsKey("key_1")) + assertTrue(pnFcmMap.containsKey("key_2")) + assertTrue(pnFcmMap.containsKey("key_3")) + assertTrue(pnFcmMap.containsKey("key_4")) + assertNotNull(pnFcmMap["key_1"]) + assertNotNull(pnFcmMap["key_2"]) + assertNotNull(pnFcmMap["key_3"]) + assertNotNull(pnFcmMap["key_4"]) + } + + @Test + fun testMicrosoft_Missing() { + val pushPayloadHelper = PushPayloadHelper() + + val mpnsPayload = MPNSPayload().apply { + backContent = "Back Content" + backTitle = "Back Title" + count = 1 + title = "Title" + type = "Type" + custom = mapOf( + "a" to "a", + "b" to 1, + "c" to "" + ) + } + pushPayloadHelper.mpnsPayload = mpnsPayload + val map = pushPayloadHelper.build() + + val pnMpnsMap = map["pn_mpns"] as HashMap<*, *> + assertNotNull(pnMpnsMap) + assertEquals(pnMpnsMap["back_content"], "Back Content") + assertEquals(pnMpnsMap["back_title"], "Back Title") + assertEquals(pnMpnsMap["count"], 1) + assertEquals(pnMpnsMap["title"], "Title") + assertEquals(pnMpnsMap["type"], "Type") + assertEquals(pnMpnsMap["a"], "a") + assertEquals(pnMpnsMap["b"], 1) + assertEquals(pnMpnsMap["c"], "") + } + + @Test + fun testMicrosoft_Valid() { + val pushPayloadHelper = PushPayloadHelper() + pushPayloadHelper.mpnsPayload = + MPNSPayload().apply { + backContent = "Back Content" + backTitle = "Back Title" + count = 1 + title = "Title" + type = "Type" + custom = mapOf( + "a" to "a", + "b" to 1, + "c" to "" + ) + } + + val map = pushPayloadHelper.build() + val pnMpnsMap = map["pn_mpns"] as Map<*, *> + + assertNotNull(pnMpnsMap) + assertEquals(pnMpnsMap["back_content"], "Back Content") + assertEquals(pnMpnsMap["back_title"], "Back Title") + assertEquals(pnMpnsMap["count"], 1) + assertEquals(pnMpnsMap["title"], "Title") + assertEquals(pnMpnsMap["type"], "Type") + assertEquals(pnMpnsMap["a"], "a") + assertEquals(pnMpnsMap["b"], 1) + assertEquals(pnMpnsMap["c"], "") + assertEquals(pnMpnsMap["d"], null) + } + + @Test + fun testMicrosoft_Empty() { + val pushPayloadHelper = PushPayloadHelper() + val mpnsPayload = MPNSPayload() + pushPayloadHelper.mpnsPayload = mpnsPayload + val map = pushPayloadHelper.build() + val pnMpnsMap = map["pn_mpns"] + assertNull(pnMpnsMap) + } + + @Test + fun testMicrosoft_Custom() { + val pushPayloadHelper = PushPayloadHelper() + pushPayloadHelper.mpnsPayload = MPNSPayload().apply { + backContent = "" + backTitle = "Back Title" + count = 1 + title = null + type = "Type" + custom = mapOf( + "a" to "a", + "b" to 1, + "c" to "" + ) + } + + val map = pushPayloadHelper.build() + + val pnMpnsMap = map["pn_mpns"] as Map<*, *> + assertNotNull(pnMpnsMap) + assertEquals("", pnMpnsMap["back_content"]) + assertEquals("Back Title", pnMpnsMap["back_title"]) + assertEquals(1, pnMpnsMap["count"]) + assertFalse(pnMpnsMap.containsKey("title")) + assertEquals("Type", pnMpnsMap["type"]) + assertEquals("a", pnMpnsMap["a"]) + assertEquals(1, pnMpnsMap["b"]) + assertEquals("", pnMpnsMap["c"]) + assertFalse(pnMpnsMap.containsKey("d")) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt new file mode 100644 index 000000000..f1a3c5b9f --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt @@ -0,0 +1,183 @@ +package com.pubnub.api.endpoints.push + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.enums.PNStatusCategory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class RemoveAllPushChannelsForDeviceTest : BaseTest() { + + @Test + fun testAppleSuccessSyncRemoveAll() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("apns", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + + @Test + fun testFirebaseSuccessSyncRemoveAll() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("gcm", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testMicrosoftSuccessSyncRemoveAll() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.MPNS + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("mpns", requests[0].queryParameter("type").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testApns2SuccessSyncRemoveAll() { + stubFor( + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + topic = "news" + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("development", requests[0].queryParameter("environment").firstValue()) + Assertions.assertEquals("news", requests[0].queryParameter("topic").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("type").isPresent) + } + + @Test + fun testIsAuthRequiredSuccessRemoveAll() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessRemoveAll() { + stubFor( + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + val success = AtomicBoolean() + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.async { _, status -> + Assertions.assertFalse(status.error) + Assertions.assertEquals(PNOperationType.PNRemoveAllPushNotificationsOperation, status.operation) + Assertions.assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertTrue(status.affectedChannels.isEmpty()) + Assertions.assertTrue(status.affectedChannelGroups.isEmpty()) + success.set(true) + } + + success.listen() + } + + @Test + fun testEmptySubscribeKeyRemoveAll() { + pubnub.configuration.subscribeKey = " " + + try { + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testNullPushTypeRemoveAll() { + try { + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + deviceId = "niceDevice" + }.sync()!! + } catch (e: PubNubException) { + assertPnException(PubNubError.PUSH_TYPE_MISSING, e) + } + } + + @Test + fun testNullDeviceIdRemoveAll() { + try { + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + pushType = PNPushType.FCM + }.sync()!! + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testEmptyDeviceIdRemoveAll() { + try { + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + pushType = PNPushType.FCM + deviceId = " " + }.sync()!! + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } +} diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt new file mode 100644 index 000000000..e4cd70f3b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt @@ -0,0 +1,228 @@ +package com.pubnub.api.endpoints.push + +import com.github.tomakehurst.wiremock.client.WireMock +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.enums.PNStatusCategory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class RemoveChannelsFromPushTest : BaseTest() { + + @Test + fun testRemoveAppleSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("apns", requests[0].queryParameter("type").firstValue()) + Assertions.assertEquals("chr1,chr2,chr3", requests[0].queryParameter("remove").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testRemoveFirebaseSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("gcm", requests[0].queryParameter("type").firstValue()) + Assertions.assertEquals("chr1,chr2,chr3", requests[0].queryParameter("remove").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testRemoveMicrosoftSuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.MPNS + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("mpns", requests[0].queryParameter("type").firstValue()) + Assertions.assertEquals("chr1,chr2,chr3", requests[0].queryParameter("remove").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("environment").isPresent) + Assertions.assertFalse(requests[0].queryParameter("topic").isPresent) + } + + @Test + fun testRemoveApns2SuccessSync() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.APNS2 + channels = listOf("chr1", "chr2", "chr3") + topic = "news" + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("chr1,chr2,chr3", requests[0].queryParameter("remove").firstValue()) + Assertions.assertEquals("development", requests[0].queryParameter("environment").firstValue()) + Assertions.assertEquals("news", requests[0].queryParameter("topic").firstValue()) + Assertions.assertFalse(requests[0].queryParameter("type").isPresent) + } + + @Test + fun testIsAuthRequiredSuccessRemove() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("chr1", "chr2", "chr3") + topic = "news" + }.sync()!! + + val requests = WireMock.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/.*"))) + Assertions.assertEquals(1, requests.size) + Assertions.assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessRemove() { + WireMock.stubFor( + WireMock.get(WireMock.urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(WireMock.aResponse().withBody("[1, \"Modified Channels\"]")) + ) + + val success = AtomicBoolean() + + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("chr1", "chr2", "chr3") + }.async { _, status -> + Assertions.assertFalse(status.error) + Assertions.assertEquals(PNOperationType.PNRemovePushNotificationsFromChannelsOperation, status.operation) + Assertions.assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + Assertions.assertEquals(status.affectedChannels, listOf("chr1", "chr2", "chr3")) + Assertions.assertTrue(status.affectedChannelGroups.isEmpty()) + success.set(true) + } + + success.listen() + } + + @Test + fun testEmptySubscribeKeyRemove() { + pubnub.configuration.subscribeKey = " " + + try { + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + pushType = PNPushType.FCM + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testNullPushType() { + try { + pubnub.removePushNotificationsFromChannels().apply { + deviceId = "niceDevice" + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.PUSH_TYPE_MISSING, e) + } + } + + @Test + fun testNullDeviceId() { + try { + pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.FCM + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testEmptyDeviceId() { + try { + pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.FCM + deviceId = " " + channels = listOf("chr1", "chr2", "chr3") + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.DEVICE_ID_MISSING, e) + } + } + + @Test + fun testMissingChannels() { + try { + pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.FCM + deviceId = "niceDevice" + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testEmptyChannels() { + try { + pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.FCM + deviceId = "niceDevice" + channels = emptyList() + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + +} \ No newline at end of file From 67df19bd50360ea6bb43ab9a7ce51c99fe5ddbfa Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:31:11 +0200 Subject: [PATCH 23/98] Implement Message Counts --- .../com/pubnub/api/endpoints/MessageCounts.kt | 63 +++++ .../consumer/history/PNMessageCountResult.kt | 5 + .../api/endpoints/history/MessageCountTest.kt | 265 ++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/history/PNMessageCountResult.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt b/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt new file mode 100644 index 000000000..42f0dfc61 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt @@ -0,0 +1,63 @@ +package com.pubnub.api.endpoints + +import com.google.gson.JsonElement +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNMessageCountResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class MessageCounts(pubnub: PubNub) : Endpoint(pubnub) { + + lateinit var channels: List + lateinit var channelsTimetoken: List + + override fun validateParams() { + super.validateParams() + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + if (!::channelsTimetoken.isInitialized || channelsTimetoken.isEmpty()) { + throw PubNubException(PubNubError.TIMETOKEN_MISSING) + } + if (channelsTimetoken.size != channels.size && channelsTimetoken.size > 1) { + throw PubNubException(PubNubError.CHANNELS_TIMETOKEN_MISMATCH) + } + } + + override fun getAffectedChannels() = channels + + override fun doWork(queryParams: HashMap): Call { + if (channelsTimetoken.size == 1) { + queryParams["timetoken"] = channelsTimetoken.toCsv() + } else { + queryParams["channelsTimetoken"] = channelsTimetoken.toCsv() + } + + return pubnub.retrofitManager.historyService.fetchCount( + subKey = pubnub.configuration.subscribeKey, + channels = channels.toCsv(), + options = queryParams + ) + } + + override fun createResponse(input: Response): PNMessageCountResult? { + val channelsMap = HashMap() + + if (pubnub.mapper.getField(input.body()!!, "channels") != null) { + val it = pubnub.mapper.getObjectIterator(input.body()!!, "channels") + while (it.hasNext()) { + val entry = it.next() + channelsMap[entry.key] = entry.value.asLong + } + return PNMessageCountResult(channelsMap) + } else { + throw PubNubException(PubNubError.HTTP_ERROR).apply { + errorMessage = "History is disabled" + } + } + } + + override fun operationType() = PNOperationType.PNMessageCountOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNMessageCountResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNMessageCountResult.kt new file mode 100644 index 000000000..606ec210a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNMessageCountResult.kt @@ -0,0 +1,5 @@ +package com.pubnub.api.models.consumer.history + +class PNMessageCountResult( + val channels: Map +) \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt new file mode 100644 index 000000000..1de7b019d --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt @@ -0,0 +1,265 @@ +package com.pubnub.api.endpoints.history + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.BaseTest +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class MessageCountTest : BaseTest() { + + @Test + fun testSyncDisabled() { + val payload = + """ + [ + "Use of the history API requires the Storage & Playback which is not enabled + for this subscribe key.Login to your PubNub Dashboard Account and enable Storage & Playback. + Contact support @pubnub.com if you require further assistance.", + 0, + 0 + ] + """.trimIndent() + + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody(payload)) + ) + + try { + pubnub.messageCounts().apply { + channels = listOf("my_channel") + channelsTimetoken = listOf(10000L) + }.sync() + } catch (ex: PubNubException) { + assertEquals("History is disabled", ex.errorMessage) + } + } + + @Test + @Throws(PubNubException::class) + fun testSingleChannelWithSingleToken() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}" + ) + ) + ) + + val response = pubnub.messageCounts().apply { + channels = listOf("my_channel") + channelsTimetoken = listOf(10000L) + }.sync()!! + + assertEquals(response.channels.size, 1) + assertFalse(response.channels.containsKey("channel_does_not_exist")) + assertTrue(response.channels.containsKey("my_channel")) + for ((key, value) in response.channels) { + assertEquals("my_channel", key) + assertEquals(java.lang.Long.valueOf("19"), value) + } + } + + @Test + fun testSingleChannelWithMultipleTokens() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}" + ) + ) + ) + var exception: PubNubException? = null + try { + pubnub.messageCounts().apply { + channels = listOf("my_channel") + channelsTimetoken = listOf(10000L, 20000L) + }.sync() + } catch (e: PubNubException) { + exception = e + } finally { + assertNotNull(exception) + assertEquals( + PubNubError.CHANNELS_TIMETOKEN_MISMATCH.message, + exception!!.pubnubError!!.message + ) + } + } + + @Test + @Throws(PubNubException::class) + fun testMultipleChannelsWithSingleToken() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + ) + ) + ) + + val response = pubnub.messageCounts().apply { + channels = listOf("my_channel", "new_channel") + channelsTimetoken = listOf(10000L) + }.sync()!! + + assertEquals(response.channels.size, 2) + assertFalse(response.channels.containsKey("channel_does_not_exist")) + assertTrue(response.channels.containsKey("my_channel")) + assertTrue(response.channels.containsKey("new_channel")) + for ((key, value) in response.channels) { + if (key == "my_channel") { + assertEquals(java.lang.Long.valueOf("19"), value) + } else if (key == "new_channel") { + assertEquals(java.lang.Long.valueOf("5"), value) + } + } + } + + @Test + @Throws(PubNubException::class) + fun testMultipleChannelsWithMultipleTokens() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + ) + ) + ) + + val response = pubnub.messageCounts().apply { + channels = listOf("my_channel", "new_channel") + channelsTimetoken = listOf(10000L, 20000L) + }.sync()!! + + assertEquals(response.channels.size, 2) + assertFalse(response.channels.containsKey("channel_does_not_exist")) + assertTrue(response.channels.containsKey("my_channel")) + assertTrue(response.channels.containsKey("new_channel")) + for ((key, value) in response.channels) { + if (key == "my_channel") { + assertEquals(java.lang.Long.valueOf("19"), value) + } else if (key == "new_channel") { + assertEquals(java.lang.Long.valueOf("5"), value) + } + } + } + + @Test + fun testWithoutTimeToken() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}" + ) + ) + ) + var exception: PubNubException? = null + try { + pubnub.messageCounts().apply { + channels = listOf("my_channel") + }.sync() + } catch (ex: PubNubException) { + exception = ex + } finally { + assertNotNull(exception) + assertEquals( + PubNubError.TIMETOKEN_MISSING.message, + exception!!.pubnubError!!.message + ) + } + } + + @Test + fun testWithoutChannelsSingleToken() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + ) + ) + ) + var exception: PubNubException? = null + try { + pubnub.messageCounts().apply { + channelsTimetoken = listOf(10000L) + }.sync() + } catch (ex: PubNubException) { + exception = ex + } finally { + assertNotNull(exception) + assertEquals( + PubNubError.CHANNEL_MISSING.message, + exception!!.pubnubError!!.message + ) + } + } + + @Test + fun testWithoutChannelsMultipleTokens() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + ) + ) + ) + var exception: PubNubException? = null + try { + pubnub.messageCounts().apply { + channelsTimetoken = listOf(10000L, 20000L) + }.sync() + } catch (ex: PubNubException) { + exception = ex + } finally { + assertNotNull(exception) + assertEquals( + PubNubError.CHANNEL_MISSING.message, + exception!!.pubnubError!!.message + ) + } + } + + @Test + fun testChannelWithSingleEmptyToken() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}" + ) + ) + ) + var exception: PubNubException? = null + try { + pubnub.messageCounts().apply { + channels = listOf("my_channel") + }.sync() + } catch (ex: PubNubException) { + exception = ex + } finally { + assertNotNull(exception) + assertEquals( + PubNubError.TIMETOKEN_MISSING.message, + exception!!.pubnubError!!.message + ) + } + } + +} \ No newline at end of file From c7c33505582dee56f3d8b903472f89db3209735f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:32:05 +0200 Subject: [PATCH 24/98] Implement Fetch Messages --- .../com/pubnub/api/endpoints/FetchMessages.kt | 131 +++++++++++++++ .../history/FetchMessagesEndpointTest.kt | 156 ++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt b/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt new file mode 100644 index 000000000..431061fbb --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt @@ -0,0 +1,131 @@ +package com.pubnub.api.endpoints + +import com.google.gson.JsonElement +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNFetchMessageItem +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult +import com.pubnub.api.models.server.FetchMessagesEnvelope +import com.pubnub.api.vendor.Crypto +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class FetchMessages(pubnub: PubNub) : Endpoint(pubnub) { + + private companion object { + private const val DEFAULT_MESSAGES = 1 + private const val MAX_MESSAGES = 25 + private const val MAX_MESSAGES_ACTIONS = 100 + } + + lateinit var channels: List + var maximumPerChannel: Int = DEFAULT_MESSAGES + var start: Long? = null + var end: Long? = null + var includeMeta = false + var includeMessageActions = false + + override fun validateParams() { + super.validateParams() + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + + if (!includeMessageActions) { + if (maximumPerChannel !in DEFAULT_MESSAGES..MAX_MESSAGES) { + when { + maximumPerChannel < DEFAULT_MESSAGES -> maximumPerChannel = DEFAULT_MESSAGES + maximumPerChannel > MAX_MESSAGES -> maximumPerChannel = MAX_MESSAGES + } + print("maximumPerChannel param defaulting to $maximumPerChannel") + } + } else { + if (maximumPerChannel !in DEFAULT_MESSAGES..MAX_MESSAGES) { + maximumPerChannel = MAX_MESSAGES_ACTIONS + print("maximumPerChannel param defaulting to $maximumPerChannel") + } + } + } + + override fun getAffectedChannels() = channels + + override fun doWork(queryParams: HashMap): Call { + queryParams["max"] = maximumPerChannel.toString() + + start?.let { + queryParams["start"] = it.toString().toLowerCase(Locale.US) + } + end?.let { + queryParams["end"] = it.toString().toLowerCase(Locale.US) + } + + if (includeMeta) { + queryParams["include_meta"] = includeMeta.toString() + } + + return if (!includeMessageActions) { + pubnub.retrofitManager.historyService.fetchMessages( + subKey = pubnub.configuration.subscribeKey, + channels = channels.toCsv(), + options = queryParams + ) + } else { + pubnub.retrofitManager.historyService.fetchMessagesWithActions( + subKey = pubnub.configuration.subscribeKey, + channel = channels.first(), + options = queryParams + ) + } + + } + + override fun createResponse(input: Response): PNFetchMessagesResult? { + val channelsMap = hashMapOf>() + + for (entry in input.body()!!.channels) { + val items = mutableListOf() + for (item in entry.value) { + items.add( + PNFetchMessageItem( + message = processMessage(item.message), + timetoken = item.timetoken, + meta = item.meta, + actions = item.actions + ) + ) + } + channelsMap[entry.key] = items + } + + return PNFetchMessagesResult(channelsMap) + } + + private fun processMessage(message: JsonElement): JsonElement { + if (!pubnub.configuration.isCipherKeyValid()) + return message + + val crypto = Crypto(pubnub.configuration.cipherKey) + + val inputText = + if (pubnub.mapper.isJsonObject(message) && pubnub.mapper.hasField(message, "pn_other")) { + pubnub.mapper.elementToString(message, "pn_other") + } else { + pubnub.mapper.elementToString(message) + } + + val outputText = crypto.decrypt(inputText!!) + + var outputObject = pubnub.mapper.fromJson(outputText, JsonElement::class.java) + + pubnub.mapper.getField(message, "pn_other")?.let { + val objectNode = pubnub.mapper.getAsObject(message) + pubnub.mapper.putOnObject(objectNode, "pn_other", outputObject) + outputObject = objectNode + } + + return outputObject + } + + override fun operationType() = PNOperationType.PNFetchMessagesOperation +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt new file mode 100644 index 000000000..00430f79e --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt @@ -0,0 +1,156 @@ +package com.pubnub.api.endpoints.history + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.BaseTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class FetchMessagesEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": [ + { + "message": "hihi", + "timetoken": "14698320467224036" + }, + { + "message": "Hey", + "timetoken": "14698320468265639" + } + ], + "mychannel": [ + { + "message": "sample message", + "timetoken": "14369823849575729" + } + ] + } + } + """.trimIndent() + ) + ) + ) + + + val response = pubnub.fetchMessages().apply { + channels = listOf("mychannel", "my_channel") + maximumPerChannel = 25 + }.sync()!! + + assertEquals(response.channels.size, 2) + assertTrue(response.channels.containsKey("mychannel")) + assertTrue(response.channels.containsKey("my_channel")) + assertEquals(response.channels["mychannel"]!!.size, 1) + assertEquals(response.channels["my_channel"]!!.size, 2) + } + + @Test + fun testSyncAuthSuccess() { + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": [ + { + "message": "hihi", + "timetoken": "14698320467224036" + }, + { + "message": "Hey", + "timetoken": "14698320468265639" + } + ], + "mychannel": [ + { + "message": "sample message", + "timetoken": "14369823849575729" + } + ] + } + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + val response = pubnub.fetchMessages().apply { + channels = listOf("mychannel", "my_channel") + maximumPerChannel = 25 + }.sync()!! + + assertEquals(response.channels.size, 2) + assertTrue(response.channels.containsKey("mychannel")) + assertTrue(response.channels.containsKey("my_channel")) + assertEquals(response.channels["mychannel"]!!.size, 1) + assertEquals(response.channels["my_channel"]!!.size, 2) + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + assertEquals(1, requests.size) + } + + @Test + fun testSyncEncryptedSuccess() { + pubnub.configuration.cipherKey = "testCipher" + + stubFor( + get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": [ + { + "message": "jC/yJ2y99BeYFYMQ7c53pg==", + "timetoken": "14797423056306675" + } + ], + "mychannel": [ + { + "message": "jC/yJ2y99BeYFYMQ7c53pg==", + "timetoken": "14797423056306675" + } + ] + } + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.fetchMessages().apply { + channels = listOf("mychannel", "my_channel") + maximumPerChannel = 25 + }.sync()!! + + assertEquals(response.channels.size, 2) + assertTrue(response.channels.containsKey("mychannel")) + assertTrue(response.channels.containsKey("my_channel")) + assertEquals(response.channels["mychannel"]!!.size, 1) + assertEquals(response.channels["my_channel"]!!.size, 1) + } + +} \ No newline at end of file From 1b49aba6f5e2a235a3c2f30b2f2e4eb7867a0754 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:33:32 +0200 Subject: [PATCH 25/98] Implement Fetch Messages --- .../models/consumer/history/PNFetchMessage.kt | 21 +++++++++++++++++++ .../models/server/FetchMessagesEnvelope.kt | 7 +++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/FetchMessagesEnvelope.kt diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt new file mode 100644 index 000000000..7f3c4bba2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt @@ -0,0 +1,21 @@ +package com.pubnub.api.models.consumer.history + +import com.google.gson.JsonElement + +class PNFetchMessagesResult( + val channels: HashMap> +) + +class PNFetchMessageItem( + val message: JsonElement, + val meta: JsonElement?, + val timetoken: Long, + val actions: Map>>? +) { + +} + +class Action( + val uuid: String, + val actionTimetoken: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/FetchMessagesEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/FetchMessagesEnvelope.kt new file mode 100644 index 000000000..381b1ccb2 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/FetchMessagesEnvelope.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.server + +import com.pubnub.api.models.consumer.history.PNFetchMessageItem + +class FetchMessagesEnvelope( + val channels: HashMap> +) \ No newline at end of file From e62c8342f13981a9b707bfef64ff3e8ee6f241b3 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:33:47 +0200 Subject: [PATCH 26/98] Implement Channel Groups --- .../channel_group/PNChannelGroupsResults.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt new file mode 100644 index 000000000..31b524ed4 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.models.consumer.channel_group + +class PNChannelGroupsAddChannelResult + +class PNChannelGroupsDeleteGroupResult + +class PNChannelGroupsRemoveChannelResult + +class PNChannelGroupsAllChannelsResult { + var channels = listOf() + internal set +} + +class PNChannelGroupsListAllResult { + var groups = listOf() + internal set +} \ No newline at end of file From d7b84cdf10315a170b813490211bbec71d142775 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:34:27 +0200 Subject: [PATCH 27/98] Add tests for History APIs --- .../com/pubnub/api/endpoints/History.kt | 107 ++-- .../consumer/history/PNHistoryItemResult.kt | 2 +- .../endpoints/history/HistoryEndpointTest.kt | 524 ++++++++++++++++++ 3 files changed, 577 insertions(+), 56 deletions(-) create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/History.kt b/src/main/kotlin/com/pubnub/api/endpoints/History.kt index 681cad76a..030712cbf 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/History.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/History.kt @@ -11,9 +11,7 @@ import com.pubnub.api.models.consumer.history.PNHistoryResult import com.pubnub.api.vendor.Crypto import retrofit2.Call import retrofit2.Response -import java.util.ArrayList -import java.util.HashMap -import java.util.Locale +import java.util.* class History(pubnub: PubNub) : Endpoint(pubnub) { @@ -31,7 +29,7 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { override fun validateParams() { super.validateParams() - if (!::channel.isInitialized) { + if (!::channel.isInitialized || channel.isBlank()) { throw PubNubException(PubNubError.CHANNEL_MISSING) } count = @@ -43,8 +41,6 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { override fun getAffectedChannels() = listOf(channel) - override fun getAffectedChannelGroups() = emptyList() - override fun doWork(queryParams: HashMap): Call { queryParams["reverse"] = reverse.toString() queryParams["include_token"] = includeTimetoken.toString() @@ -66,65 +62,70 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { } override fun createResponse(input: Response): PNHistoryResult? { + val startTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(input.body()!!, 1)) + val endTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(input.body()!!, 2)) + + val messages = mutableListOf() + + var historyData = PNHistoryResult( + messages = messages, + startTimetoken = startTimeToken, + endTimetoken = endTimeToken + ) - var result: PNHistoryResult? = null - val messages = ArrayList() - - input.body()?.let { - val startTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(it, 1)) - val endTimeToken = pubnub.mapper.elementToLong(pubnub.mapper.getArrayElement(it, 2)) - - pubnub.mapper.getArrayElement(it, 0).run { - if (this.isJsonArray) { - pubnub.mapper.getArrayIterator(this)?.forEach { - - var message: JsonElement? - var timetoken: Long? = null - var meta: JsonElement? = null - - if (includeTimetoken || includeMeta) { - if (includeTimetoken) { - timetoken = pubnub.mapper.elementToLong(it, "timetoken") - } - if (includeMeta) { - meta = pubnub.mapper.getField(it, "meta") - } - message = processMessage(pubnub.mapper.getField(it, "message")!!) - } else { - message = processMessage(it) - } - - messages += PNHistoryItemResult( - message, - timetoken, - meta - ) + if (pubnub.mapper.getArrayElement(input.body()!!, 0).isJsonArray) { + val iterator = pubnub.mapper.getArrayIterator(pubnub.mapper.getArrayElement(input.body()!!, 0))!! + while (iterator.hasNext()) { + + val historyEntry = iterator.next() + + var message: JsonElement + var timetoken: Long? = null + var meta: JsonElement? = null + + if (includeTimetoken || includeMeta) { + message = processMessage(pubnub.mapper.getField(historyEntry, "message")!!) + if (includeTimetoken) { + timetoken = pubnub.mapper.elementToLong(historyEntry, "timetoken") + } + if (includeMeta) { + meta = pubnub.mapper.getField(historyEntry, "meta") } + } else { + message = processMessage(historyEntry) } - } - result = PNHistoryResult( - messages, - startTimeToken, - endTimeToken - ) + val historyItem = PNHistoryItemResult( + entry = message, + timetoken = timetoken, + meta = meta + ) + + messages.add(historyItem) + } + } else { + throw PubNubException(PubNubError.HTTP_ERROR).apply { + errorMessage = "History is disabled" + } } - return result + return historyData } - @Throws(PubNubException::class) private fun processMessage(message: JsonElement): JsonElement { if (!pubnub.configuration.isCipherKeyValid()) return message val crypto = Crypto(pubnub.configuration.cipherKey) - val inputText = pubnub.mapper.getField(message, "pn_other")?.let { - pubnub.mapper.elementToString(it) - }.run { - pubnub.mapper.elementToString(message) - } + val inputText = + if (pubnub.mapper.isJsonObject(message) && pubnub.mapper.hasField(message, "pn_other")) { + pubnub.mapper.elementToString(message, "pn_other") + } else { + pubnub.mapper.elementToString(message) + } + + println("InputtTextt $inputText") val outputText = crypto.decrypt(inputText!!) @@ -140,8 +141,4 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { } override fun operationType() = PNOperationType.PNHistoryOperation - - override fun isSubKeyRequired() = true - override fun isPubKeyRequired() = false - override fun isAuthRequired() = true } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt index 11257526e..048c3214e 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryItemResult.kt @@ -2,7 +2,7 @@ package com.pubnub.api.models.consumer.history import com.google.gson.JsonElement -data class PNHistoryItemResult( +class PNHistoryItemResult( val entry: JsonElement, val timetoken: Long? = null, val meta: JsonElement? = null diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt new file mode 100644 index 000000000..6b991db9f --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt @@ -0,0 +1,524 @@ +package com.pubnub.api.endpoints.history + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class HistoryEndpointTest : BaseTest() { + + @Test + fun testSyncDisabled() { + val payload = """ + [ + "Use of the history API requires the Storage & Playback which is not enabled for this subscribe key. Login to your PubNub Dashboard Account and enable Storage & Playback. Contact support@pubnub.com if you require further assistance.", + 0, + 0 + ] + """.trimIndent() + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(payload)) + ) + + try { + pubnub.history().apply { + channel = "niceChannel" + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.HTTP_ERROR, e) + assertEquals("History is disabled", e.errorMessage) + } + } + + @Test + fun testSyncWithTokensDisabled() { + val payload = """ + [ + "Use of the history API requires the Storage & Playback which is not enabled for this subscribe key. Login to your PubNub Dashboard Account and enable Storage & Playback. Contact support@pubnub.com if you require further assistance.", + 0, + 0 + ] + """.trimIndent() + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(payload)) + ) + + try { + pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = true + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.HTTP_ERROR, e) + assertEquals("History is disabled", e.errorMessage) + } + } + + @Test + fun testSyncSuccess() { + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyEnvelope1 = mapOf( + "timetoken" to 1111, + "message" to historyItem1 + ) + + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + val historyEnvelope2 = mapOf( + "timetoken" to 2222, + "message" to historyItem2 + ) + + val historyItems = listOf( + historyEnvelope1, + historyEnvelope2 + ) + val testArray = listOf( + historyItems, + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = true + }.sync()!! + + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) + + assertEquals(2, messages.size) + + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } + + @Test + fun testSyncAuthSuccess() { + pubnub.configuration.authKey = "authKey" + + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyEnvelope1 = mapOf( + "timetoken" to 1111, + "message" to historyItem1 + ) + + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + val historyEnvelope2 = mapOf( + "timetoken" to 2222, + "message" to historyItem2 + ) + + val historyItems = listOf( + historyEnvelope1, + historyEnvelope2 + ) + val testArray = listOf( + historyItems, + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = true + }.sync()!! + + val requests = findAll(getRequestedFor(anyUrl())) + assertEquals(1, requests.size) + assertEquals("authKey", requests.first().queryParameter("auth").firstValue()) + } + + @Test + fun testSyncEncryptedSuccess() { + pubnub.configuration.cipherKey = "testCipher" + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn( + aResponse().withBody( + """ + [ + [ + "EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\n", + "EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\n", + "EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\n" + ], + 14606134331557852, + 14606134485013970 + ] + """.trimIndent() + ) + ) + ) + + val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = false + }.sync()!! + + assertEquals(14606134331557852, startTimetoken) + assertEquals(14606134485013970, endTimetoken) + + assertEquals(3, messages.size) + + assertNull(messages[0].timetoken) + + assertEquals("m1", messages[0].entry.asJsonArray[0].asString) + assertEquals("m2", messages[0].entry.asJsonArray[1].asString) + assertEquals("m3", messages[0].entry.asJsonArray[2].asString) + + assertEquals("m1", messages[1].entry.asJsonArray[0].asString) + assertEquals("m2", messages[1].entry.asJsonArray[1].asString) + assertEquals("m3", messages[1].entry.asJsonArray[2].asString) + + assertEquals("m1", messages[2].entry.asJsonArray[0].asString) + assertEquals("m2", messages[2].entry.asJsonArray[1].asString) + assertEquals("m3", messages[2].entry.asJsonArray[2].asString) + } + + @Test + fun testSyncEncryptedWithPNOtherSuccess() { + pubnub.configuration.cipherKey = "hello" + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn( + aResponse().withBody( + """ + [ + [ + { + "pn_other": "6QoqmS9CnB3W9+I4mhmL7w==" + } + ], + 14606134331557852, + 14606134485013970 + ] + """.trimIndent() + ) + ) + ) + + + val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = false + }.sync()!! + + assertEquals(14606134331557852, startTimetoken) + assertEquals(14606134485013970, endTimetoken) + + assertEquals(1, messages.size) + + assertNull(messages[0].timetoken) + assertEquals("hey", messages[0].entry.asJsonObject.get("pn_other").asJsonObject.get("text").asString) + + } + + @Test + fun testSyncSuccessWithoutTimeToken() { + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + + val testArray = listOf( + listOf( + historyItem1, + historyItem2 + ), + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + channel = "niceChannel" + }.sync()!! + + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) + + assertEquals(2, messages.size) + + assertNull(messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + + assertNull(messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } + + @Test + fun testMissingChannel() { + try { + pubnub.history().apply { + + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testChannelIsEmpty() { + try { + pubnub.history().apply { + channel = "" + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testChannelIsBlank() { + try { + pubnub.history().apply { + channel = " " + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testOperationTypeSuccessAsync() { + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyEnvelope1 = mapOf( + "timetoken" to 1111, + "message" to historyItem1 + ) + + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + val historyEnvelope2 = mapOf( + "timetoken" to 2222, + "message" to historyItem2 + ) + + val historyItems = listOf( + historyEnvelope1, + historyEnvelope2 + ) + val testArray = listOf( + historyItems, + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + val success = AtomicBoolean() + + pubnub.history().apply { + channel = "niceChannel" + includeTimetoken = true + }.async { result, status -> + + assertEquals(PNOperationType.PNHistoryOperation, status.operation) + + val (messages, startTimetoken, endTimetoken) = result!! + + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) + + assertEquals(2, messages.size) + + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + + success.set(true) + } + + success.listen() + } + + @Test + fun testSyncCountReverseStartEndSuccess() { + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyEnvelope1 = mapOf( + "timetoken" to 1111, + "message" to historyItem1 + ) + + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + val historyEnvelope2 = mapOf( + "timetoken" to 2222, + "message" to historyItem2 + ) + + val historyItems = listOf( + historyEnvelope1, + historyEnvelope2 + ) + val testArray = listOf( + historyItems, + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + channel = "niceChannel" + count = 5 + reverse = true + start = 1L + end = 2L + includeTimetoken = true + }.sync()!! + + val requests = findAll( + getRequestedFor( + urlMatching + ("/v2/history/sub-key/mySubscribeKey/channel/niceChannel.*") + ) + ) + + assertEquals(1, requests.size) + + assertEquals("true", requests.first().queryParameter("reverse").firstValue()) + assertEquals("5", requests.first().queryParameter("count").firstValue()) + assertEquals("1", requests.first().queryParameter("start").firstValue()) + assertEquals("2", requests.first().queryParameter("end").firstValue()) + assertEquals("true", requests.first().queryParameter("include_token").firstValue()) + + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) + + assertEquals(2, messages.size) + + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + + } + + @Test + fun testSyncProcessMessageError() { + val historyItem1 = mapOf( + "a" to 11, + "b" to 22 + ) + val historyEnvelope1 = mapOf( + "timetoken" to 1111, + "message" to historyItem1 + ) + + val historyItem2 = mapOf( + "a" to 33, + "b" to 44 + ) + val historyEnvelope2 = mapOf( + "timetoken" to 2222, + "message" to historyItem2 + ) + + val historyItems = listOf( + historyEnvelope1, + historyEnvelope2 + ) + val testArray = listOf( + historyItems, + 1234, + 4321 + ) + + stubFor( + get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) + ) + + pubnub.configuration.cipherKey = "Test" + + try { + pubnub.history().apply { + channel = "niceChannel" + count = 5 + reverse = true + start = 1L + end = 2L + includeTimetoken = true + }.sync()!! + failTest() + } catch (e: UnsupportedOperationException) { + + } + } + +} \ No newline at end of file From 34add896bfd0d661f49970a5d11934f426972e97 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:35:27 +0200 Subject: [PATCH 28/98] Improve PublishSequenceManager --- .../api/managers/PublishSequenceManager.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt b/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt index f32144bed..589d0473b 100644 --- a/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/PublishSequenceManager.kt @@ -1,17 +1,17 @@ package com.pubnub.api.managers -internal class PublishSequenceManager { +internal class PublishSequenceManager(private val maxSequence: Int) { - private val maxSequence = 65535 private var nextSequence = 0 - @Synchronized internal fun nextSequence(): Int { - if (maxSequence == nextSequence) { - nextSequence = 1 - } else { - nextSequence++ + synchronized(nextSequence) { + if (maxSequence == nextSequence) { + nextSequence = 1 + } else { + nextSequence++ + } + return nextSequence } - return nextSequence } } \ No newline at end of file From 09e897d962c5f6ff7eef8675056c103870ad0502 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:37:06 +0200 Subject: [PATCH 29/98] Improve Reconnection Manager --- .../pubnub/api/managers/ReconnectionManager.kt | 15 ++++++++------- .../pubnub/api/managers/SubscriptionManager.kt | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt index e53aeae73..8f6e72344 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt @@ -4,9 +4,7 @@ import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.callbacks.ReconnectionCallback import com.pubnub.api.enums.PNReconnectionPolicy -import java.util.Calendar -import java.util.Timer -import java.util.TimerTask +import java.util.* import kotlin.math.pow class ReconnectionManager(val pubnub: PubNub) { @@ -85,13 +83,16 @@ class ReconnectionManager(val pubnub: PubNub) { } private fun callTime() { - // todo reconnection pubnub.time() - .async { _, status -> + .async { result, status -> if (!status.error) { - + stopHeartbeatTimer() + reconnectionCallback.onReconnection() } else { - + println("callTime at ${System.currentTimeMillis()}") + exponentialMultiplier++ + failedCalls++ + registerHeartbeatTimer() } } } diff --git a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt index a7f86491c..85e51f1b7 100644 --- a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt @@ -109,12 +109,12 @@ class SubscriptionManager(val pubnub: PubNub) { } @Synchronized - fun getSubscribedChannels(): List? { + fun getSubscribedChannels(): List { return subscriptionState.prepareChannelList(false) } @Synchronized - fun getSubscribedChannelGroups(): List? { + fun getSubscribedChannelGroups(): List { return subscriptionState.prepareChannelGroupList(false) } From bb753e68f2c723ce8c947c357559e4fe756d0678 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:38:10 +0200 Subject: [PATCH 30/98] Register Message Actions within the listener --- .../kotlin/com/pubnub/api/managers/ListenerManager.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt index 9d47814ad..f303c7e93 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt @@ -3,10 +3,12 @@ package com.pubnub.api.managers import com.pubnub.api.PubNub import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult -import java.util.ArrayList +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import java.util.* class ListenerManager(val pubnub: PubNub) { private val listeners = mutableListOf() @@ -48,5 +50,9 @@ class ListenerManager(val pubnub: PubNub) { getListeners().forEach { it.signal(pubnub, signal) } } + fun announce(messageAction: PNMessageActionResult) { + getListeners().forEach { it.messageAction(pubnub, messageAction) } + } + } \ No newline at end of file From 7dd39de05328d7659957b522cc51ef574f5e512f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:38:49 +0200 Subject: [PATCH 31/98] Improve JSON Mapper Manager --- .../com/pubnub/api/managers/MapperManager.kt | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt index 9cc3dafd6..998aff05e 100644 --- a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -1,20 +1,14 @@ package com.pubnub.api.managers -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParseException -import com.google.gson.TypeAdapter -import com.google.gson.internal.bind.TypeAdapters.BOOLEAN -import com.google.gson.internal.bind.TypeAdapters.NUMBER -import com.google.gson.internal.bind.TypeAdapters.STRING +import com.google.gson.* +import com.google.gson.internal.bind.TypeAdapters.* import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import retrofit2.Converter import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.Type class MapperManager { @@ -88,10 +82,19 @@ class MapperManager { fun getAsArray(element: JsonElement) = element.asJsonArray - @Throws(PubNubException::class) fun fromJson(input: String?, clazz: Class): T { return try { - this.objectMapper.fromJson(input, clazz) + this.objectMapper.fromJson(input, clazz) + } catch (e: JsonParseException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.message + } + } + } + + fun fromJson(input: String?, typeOfT: Type): T { + return try { + this.objectMapper.fromJson(input, typeOfT) } catch (e: JsonParseException) { throw PubNubException(PubNubError.PARSING_ERROR).apply { errorMessage = e.message @@ -108,8 +111,7 @@ class MapperManager { return fromJson(toJson(obj), clazz) }*/ - @Throws(PubNubException::class) - fun toJson(input: Any): String { + fun toJson(input: Any?): String { return try { this.objectMapper.toJson(input) } catch (e: JsonParseException) { From dcbd9a2ee666957ad633ea6ad53c269598f2c4a6 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:39:09 +0200 Subject: [PATCH 32/98] Register services for new endpoints --- .../com/pubnub/api/managers/RetrofitManager.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt index 33ca2d42d..9e36a6218 100644 --- a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt @@ -2,11 +2,7 @@ package com.pubnub.api.managers import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity -import com.pubnub.api.services.HistoryService -import com.pubnub.api.services.PresenceService -import com.pubnub.api.services.PublishService -import com.pubnub.api.services.SubscribeService -import com.pubnub.api.services.TimeService +import com.pubnub.api.services.* import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit @@ -28,6 +24,11 @@ class RetrofitManager(val pubnub: PubNub) { internal val publishService: PublishService internal val historyService: HistoryService internal val presenceService: PresenceService + internal val messageActionService: MessageActionService + internal val signalService: SignalService + internal val channelGroupService: ChannelGroupService + internal val pushService: PushService + internal val userService: UserService internal val subscribeService: SubscribeService @@ -40,6 +41,11 @@ class RetrofitManager(val pubnub: PubNub) { publishService = transactionInstance.create(PublishService::class.java) historyService = transactionInstance.create(HistoryService::class.java) presenceService = transactionInstance.create(PresenceService::class.java) + messageActionService = transactionInstance.create(MessageActionService::class.java) + signalService = transactionInstance.create(SignalService::class.java) + channelGroupService = transactionInstance.create(ChannelGroupService::class.java) + pushService = transactionInstance.create(PushService::class.java) + userService = transactionInstance.create(UserService::class.java) subscribeService = subscriptionInstance.create(SubscribeService::class.java) } From 472bec44aef949532592a10a68344ac5477f43cd Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:39:56 +0200 Subject: [PATCH 33/98] Minor refactor on network services --- .../com/pubnub/api/services/HistoryService.kt | 23 +++++++++++++++++++ .../pubnub/api/services/PresenceService.kt | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt index 50b0b8c02..3049eadc0 100644 --- a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt +++ b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt @@ -1,6 +1,7 @@ package com.pubnub.api.services import com.google.gson.JsonElement +import com.pubnub.api.models.server.FetchMessagesEnvelope import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path @@ -14,4 +15,26 @@ interface HistoryService { @Path("channel") channel: String, @QueryMap options: Map ): Call + + @GET("v3/history/sub-key/{subKey}/channel/{channels}") + fun fetchMessages( + @Path("subKey") subKey: String, + @Path("channels") channels: String, + @QueryMap options: Map + ): Call + + @GET("v3/history-with-actions/sub-key/{subKey}/channel/{channel}") + fun fetchMessagesWithActions( + @Path("subKey") subKey: String, + @Path("channel") channel: String, + @QueryMap options: Map + ): Call + + @GET("v3/history/sub-key/{subKey}/message-counts/{channels}") + fun fetchCount( + @Path("subKey") subKey: String, + @Path("channels") channels: String, + @QueryMap options: Map + ): Call + } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt index 3f52c4040..b9be3e293 100644 --- a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt +++ b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt @@ -15,14 +15,14 @@ interface PresenceService { @Path("subKey") subKey: String, @Path("channel") channel: String, @QueryMap options: Map - ): Call> + ): Call @GET("v2/presence/sub-key/{subKey}/channel/{channel}/heartbeat") fun heartbeat( @Path("subKey") subKey: String, @Path("channel") channel: String, @QueryMap(encoded = true) options: Map - ): Call> + ): Call> @GET("v2/presence/sub-key/{subKey}/uuid/{uuid}") fun whereNow( From e1253d72fd5b1be5f09b6c4da6fcea66255c177c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:40:51 +0200 Subject: [PATCH 34/98] Remove Kotlin Crypto manager --- .../java/com/pubnub/api/vendor/Crypto.java | 149 ++++++++++++++++++ .../kotlin/com/pubnub/api/vendor/Crypto.kt | 135 ---------------- 2 files changed, 149 insertions(+), 135 deletions(-) create mode 100644 src/main/java/com/pubnub/api/vendor/Crypto.java delete mode 100644 src/main/kotlin/com/pubnub/api/vendor/Crypto.kt diff --git a/src/main/java/com/pubnub/api/vendor/Crypto.java b/src/main/java/com/pubnub/api/vendor/Crypto.java new file mode 100644 index 000000000..ee277c8e7 --- /dev/null +++ b/src/main/java/com/pubnub/api/vendor/Crypto.java @@ -0,0 +1,149 @@ +package com.pubnub.api.vendor; + +import com.google.gson.Gson; +import com.pubnub.api.PubNubError; +import com.pubnub.api.PubNubException; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.spec.AlgorithmParameterSpec; + + +public class Crypto { + + byte[] keyBytes = null; + byte[] ivBytes = null; + String initializationVector = "0123456789012345"; + String cipherKey; + boolean INIT = false; + + public Crypto(String cipherKey) { + this.cipherKey = cipherKey; + } + + public Crypto(String cipherKey, String customInitializationVector) { + if (customInitializationVector != null) { + this.initializationVector = customInitializationVector; + } + + this.cipherKey = cipherKey; + } + + public void initCiphers() throws PubNubException { + if (INIT) + return; + try { + + keyBytes = new String(hexEncode(sha256(this.cipherKey.getBytes("UTF-8"))), "UTF-8") + .substring(0, 32) + .toLowerCase().getBytes("UTF-8"); + ivBytes = initializationVector.getBytes("UTF-8"); + INIT = true; + } catch (UnsupportedEncodingException e) { + throw newCryptoError(11, e); + } + } + + public static byte[] hexEncode(byte[] input) throws PubNubException { + StringBuffer result = new StringBuffer(); + for (byte byt : input) + result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1)); + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw newCryptoError(12, e); + } + } + + private static PubNubException newCryptoError(int code, Exception exception) { + PubNubException pubNubException = new PubNubException(); + pubNubException.setPubnubError(PubNubError.CRYPTO_ERROR); + pubNubException.setErrorMessage(exception.getClass().getSimpleName() + " " + exception.getMessage() + " " + code); + return pubNubException; + } + + public String encrypt(String input) throws PubNubException { + try { + initCiphers(); + AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); + SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); + Cipher cipher = null; + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec); + return new String(Base64.encode(cipher.doFinal(input.getBytes("UTF-8")), 0), Charset.forName("UTF-8")); + } catch (Exception e) { + throw newCryptoError(0, e); + } + + } + + /** + * Decrypt + * + * @param cipher_text + * @return String + * @throws PubNubException + */ + public String decrypt(String cipher_text) throws PubNubException { + try { + initCiphers(); + AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); + SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec); + return new String(cipher.doFinal(Base64.decode(cipher_text, 0)), "UTF-8"); + } catch (Exception e) { + throw newCryptoError(0, e); + } + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + /** + * Get MD5 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + public static byte[] md5(String input) throws PubNubException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + byte[] hashedBytes = digest.digest(input.getBytes("UTF-8")); + return hashedBytes; + } catch (Exception e) { + throw newCryptoError(0, e); + } + } + + /** + * Get SHA256 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + public static byte[] sha256(byte[] input) throws PubNubException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + byte[] hashedBytes = digest.digest(input); + return hashedBytes; + } catch (Exception e) { + throw newCryptoError(0, e); + } + } + +} diff --git a/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt b/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt deleted file mode 100644 index 066f3ccb4..000000000 --- a/src/main/kotlin/com/pubnub/api/vendor/Crypto.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.pubnub.api.vendor - -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException -import java.io.UnsupportedEncodingException -import java.nio.charset.Charset -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec -import kotlin.experimental.and - -internal class Crypto(internal var cipherKey: String) { - - private var keyBytes: ByteArray? = null - private var ivBytes: ByteArray? = null - private var initializationVector = "0123456789012345" - private var initialized = false - - - @Throws(PubNubException::class) - fun initCiphers() { - if (initialized) - return - try { - - keyBytes = String(hexEncode(sha256(this.cipherKey.toByteArray(charset("UTF-8")))), charset("UTF-8")) - .substring(0, 32) - .toLowerCase().toByteArray(charset("UTF-8")) - ivBytes = initializationVector.toByteArray(charset("UTF-8")) - initialized = true - } catch (e: UnsupportedEncodingException) { - throw throwCryptoError(e, 11) - } - } - - @Throws(PubNubException::class) - fun hexEncode(input: ByteArray): ByteArray { - val result = StringBuffer() - input.forEach { - result.append(((it and 0xff.toByte()) + 0x100).toString(16).substring(1)) - } - try { - return result.toString().toByteArray(charset("UTF-8")) - } catch (e: UnsupportedEncodingException) { - throw throwCryptoError(e, 12) - } - } - - @Throws(PubNubException::class) - fun encrypt(input: String): String { - try { - initCiphers() - val ivSpec = IvParameterSpec(ivBytes) - val newKey = SecretKeySpec(keyBytes, "AES") - var cipher: Cipher? = null - cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher!!.init(Cipher.ENCRYPT_MODE, newKey, ivSpec) - return String( - Base64.encode(cipher.doFinal(input.toByteArray(charset("UTF-8"))), 0), - Charset.forName("UTF-8") - ) - } catch (e: Exception) { - throw throwCryptoError(e) - } - } - - /** - * Decrypt - * - * @param cipher_text - * @return String - * @throws PubNubException - */ - @Throws(PubNubException::class) - fun decrypt(cipher_text: String): String { - try { - initCiphers() - val ivSpec = IvParameterSpec(ivBytes) - val newKey = SecretKeySpec(keyBytes, "AES") - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec) - return String(cipher.doFinal(Base64.decode(cipher_text, 0)), charset("UTF-8")) - } catch (e: Exception) { - throw throwCryptoError(e) - } - } - - /** - * Get MD5 - * - * @param input - * @return byte[] - * @throws PubNubException - */ - @Throws(PubNubException::class) - fun md5(input: String): ByteArray { - val digest: MessageDigest - try { - digest = MessageDigest.getInstance("MD5") - return digest.digest(input.toByteArray(charset("UTF-8"))) - } catch (e: NoSuchAlgorithmException) { - throw throwCryptoError(e, 118) - } catch (e: UnsupportedEncodingException) { - throw throwCryptoError(e, 119) - } - - } - - /** - * Get SHA256 - * - * @param input - * @return byte[] - * @throws PubNubException - */ - @Throws(PubNubException::class) - fun sha256(input: ByteArray): ByteArray { - val digest: MessageDigest - try { - digest = MessageDigest.getInstance("SHA-256") - return digest.digest(input) - } catch (e: NoSuchAlgorithmException) { - throw throwCryptoError(e, 111) - } - } - - private fun throwCryptoError(exception: Exception, code: Int? = null): PubNubException { - return PubNubException(PubNubError.CRYPTO_ERROR).apply { - errorMessage += " ${exception.message}, $code" - } - } - -} \ No newline at end of file From d39da416e1fd10e40fbbf3404dae0e3a99efd907 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:41:41 +0200 Subject: [PATCH 35/98] Add tests for Subscribe APIs --- .../api/workers/SubscribeMessageWorker.kt | 29 +- .../endpoints/pubsub/SubscribeEndpointTest.kt | 556 +++ .../api/managers/SubscriptionManagerTest.kt | 3428 +++++++++++++++++ 3 files changed, 4007 insertions(+), 6 deletions(-) create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt diff --git a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt index 8e11248fc..beb2ba44a 100644 --- a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt +++ b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt @@ -9,10 +9,13 @@ import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.managers.DuplicationManager import com.pubnub.api.managers.ListenerManager import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.api.models.consumer.pubsub.BasePubSubResult import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.ObjectPayload import com.pubnub.api.models.server.PresenceEnvelope import com.pubnub.api.models.server.SubscribeMessage import com.pubnub.api.vendor.Crypto @@ -125,9 +128,21 @@ class SubscribeMessageWorker( TYPE_OBJECT -> { } + TYPE_MESSAGE_ACTION -> { + val objectPayload = pubnub.mapper.convertValue(extractedMessage, ObjectPayload::class.java) + val data = objectPayload.data.asJsonObject + if (!data.has("uuid")) { + data.addProperty("uuid", result.publisher) + } + listenerManager.announce( + PNMessageActionResult( + result = result, + event = objectPayload.event, + data = pubnub.mapper.convertValue(data, PNMessageAction::class.java) + ) + ) + } } - - } } @@ -148,10 +163,12 @@ class SubscribeMessageWorker( val crypto = Crypto(pubnub.configuration.cipherKey) - var inputText = pubnub.mapper.elementToString(input, "pn_other")?.let { it } - if (inputText == null) { - inputText = pubnub.mapper.elementToString(input) - } + val inputText = + if (pubnub.mapper.isJsonObject(input!!) && pubnub.mapper.hasField(input, "pn_other")) { + pubnub.mapper.elementToString(input, "pn_other") + } else { + pubnub.mapper.elementToString(input) + } val outputText = try { diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt new file mode 100644 index 000000000..4fd412204 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt @@ -0,0 +1,556 @@ +package com.pubnub.api.endpoints.pubsub + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.BaseTest +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.assertPnException +import com.pubnub.api.models.server.SubscribeMessage +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class SubscribeEndpointTest : BaseTest() { + + @Test + fun subscribeChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "someSubKey", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + val subscribeEnvelope = Subscribe(pubnub).apply { + channels = listOf("coolChannel") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", subscribeEnvelope.metadata.region) + assertEquals(14607577960932487L, subscribeEnvelope.metadata.timetoken) + assertEquals(1, subscribeEnvelope.messages.size) + + val subscribeMessage: SubscribeMessage = subscribeEnvelope.messages[0] + assertEquals("4", subscribeMessage.shard) + assertEquals("0", subscribeMessage.flags) + assertEquals("coolChannel", subscribeMessage.channel) + assertEquals("coolChan-bnel", subscribeMessage.subscriptionMatch) + assertEquals("someSubKey", subscribeMessage.subscribeKey) + assertEquals("Client-g5d4g", subscribeMessage.issuingClientId) + assertEquals("""{"text":"Enter Message Here"}""", subscribeMessage.payload.toString()) + } + + @Test + fun subscribeChannelsSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "someSubKey", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + val subscribeEnvelope = Subscribe(pubnub).apply { + channels = listOf("coolChannel") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", subscribeEnvelope.metadata.region) + assertTrue(subscribeEnvelope.metadata.timetoken == 14607577960932487L) + assertEquals(1, subscribeEnvelope.messages.size) + + val subscribeMessage: SubscribeMessage = subscribeEnvelope.messages[0] + assertEquals("4", subscribeMessage.shard) + assertEquals("0", subscribeMessage.flags) + assertEquals("coolChannel", subscribeMessage.channel) + assertEquals("coolChan-bnel", subscribeMessage.subscriptionMatch) + assertEquals("someSubKey", subscribeMessage.subscribeKey) + assertEquals("Client-g5d4g", subscribeMessage.issuingClientId) + assertEquals("""{"text":"Enter Message Here"}""", subscribeMessage.payload.toString()) + } + + @Test + fun subscribeChannelsAuthSync() { + pubnub.configuration.authKey = "authKey" + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + + } + + @Test + fun subscribeChannelsWithGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + channelGroups = listOf("cg1") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun subscribeGroupsSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1,cg2", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + @Throws(PubNubException::class) + fun subscribeGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channelGroups = listOf("cg1") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + @Throws(PubNubException::class) + fun subscribeWithTimeTokenSync() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channelGroups = listOf("cg1") + timetoken = 1337L + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + assertEquals("1337", requests[0].queryParameter("tt").firstValue()) + } + + @Test + fun subscribeWithFilter() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Kotlin.*")) + .withQueryParam("filter-expr", matching("this=1&that=cool")) + .withQueryParam("channel-group", matching("cg1")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channelGroups = listOf("cg1") + filterExpression = "this=1&that=cool" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun subscribeWithRegion() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channelGroups = listOf("cg1") + region = "10" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + assertEquals("10", requests[0].queryParameter("tr").firstValue()) + } + + @Test + fun subscribeMissingChannelAndGroupSync() { + try { + Subscribe(pubnub).apply { + + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_AND_GROUP_MISSING, e) + } + } + + @Test + fun testMissingSubKeySync() { + pubnub.configuration.subscribeKey = " " + + try { + Subscribe(pubnub).apply { + + }.sync()!! + throw RuntimeException() + } catch (e: PubNubException) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun stopAndReconnect() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + Subscribe(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + }.sync()!! + + pubnub.disconnect() + pubnub.reconnect() + + Subscribe(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(2, requests.size) + } + + @Test + fun testSuccessIncludeState() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch2/0")) + .willReturn(aResponse().withStatus(200)) + ) + + try { + Subscribe(pubnub).apply { + channels = listOf("ch1", "ch2") + state = mapOf( + "CH1" to "this-is-channel1", + "CH2" to "this-is-channel2" + ) + }.sync() + } catch (e: Exception) { + // e.printStackTrace() + } + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + assertEquals("123", request.queryParameter("heartbeat").firstValue()) + assertEquals( + """{"CH1":"this-is-channel1","CH2":"this-is-channel2"}""", + request.queryParameter("state").firstValue() + ) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt b/src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt new file mode 100644 index 000000000..a7f8ce9dc --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt @@ -0,0 +1,3428 @@ +package com.pubnub.api.managers + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.reflect.TypeToken +import com.pubnub.api.* +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNHeartbeatNotificationOptions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult +import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult +import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import org.awaitility.Awaitility +import org.hamcrest.Matchers +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class SubscriptionManagerTest : BaseTest() { + + @Test + fun testGetSubscribedChannels() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + val channels = pubnub.getSubscribedChannels() + assertTrue(channels.contains("ch1")) + assertTrue(channels.contains("ch2")) + } + + @Test + fun testGetSubscribedEmptyChannel() { + val gotMessages = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.subscribe().apply { + channels = listOf("") + }.execute() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + gotMessages.addAndGet(1) + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + gotMessages.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + gotMessages.addAndGet(1) + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessages, IsEqual.equalTo(0)) + } + + @Test + fun testGetSubscribedEmptyChannelGroup() { + val gotMessages = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.subscribe().apply { + channelGroups = listOf("") + }.execute() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + gotMessages.addAndGet(1) + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + gotMessages.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + gotMessages.addAndGet(1) + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessages, IsEqual.equalTo(0)) + } + + @Test + fun testGetSubscribedChannelGroups() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.subscribe().apply { + channelGroups = listOf("cg1", "cg2") + }.execute() + + val groups: List = pubnub.getSubscribedChannelGroups() + assertTrue(groups.contains("cg1")) + assertTrue(groups.contains("cg2")) + } + + @Test + fun testPubNubUnsubscribeAll() { + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1", "cg2") + withPresence = true + }.execute() + + var channels = pubnub.getSubscribedChannels() + assertTrue(channels.contains("ch1")) + assertTrue(channels.contains("ch2")) + + var groups = pubnub.getSubscribedChannelGroups() + assertTrue(groups.contains("cg1")) + assertTrue(groups.contains("cg2")) + + pubnub.unsubscribeAll() + + channels = pubnub.getSubscribedChannels() + assertEquals(0, channels.size) + + groups = pubnub.getSubscribedChannelGroups() + assertEquals(0, groups.size) + } + + @Test + fun testSubscribeBuilder() { + val gotStatus = AtomicInteger() + + val gotMessage = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size > 0) + assertEquals("Message", pubnub.mapper.elementToString(pnMessageResult.message, "text")) + assertEquals("coolChannel", pnMessageResult.channel) + assertEquals(null, pnMessageResult.subscription) + assertEquals("Publisher-A", pnMessageResult.publisher) + gotMessage.set(true) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessage, IsEqual.equalTo(true)) + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribeDuplicateDisabledBuilder() { + val gotMessages = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + }, + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + gotMessages.addAndGet(1) + } + + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessages, IsEqual.equalTo(2)) + } + + @Test + fun testSubscribeDuplicateBuilder() { + pubnub.configuration.dedupOnSubscribe = true + + val gotMessages = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + }, + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + gotMessages.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessages, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribeDuplicateWithLimitBuilder() { + pubnub.configuration.dedupOnSubscribe = true + pubnub.configuration.maximumMessagesCacheSize = 1 + + val gotMessages = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message1" + }, + "b": "coolChannel" + }, + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message2" + }, + "b": "coolChannel" + }, + { + "a": "4", + "f": 0, + "i": "Publisher-A", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message1" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + gotMessages.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAtomic(gotMessages, IsEqual.equalTo(3)) + } + + @Test + fun testQueueNotificationsBuilderNoThresholdSpecified() { + pubnub.configuration.requestMessageCountThreshold = null + + val gotStatus = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(false)) + } + + @Test + fun testQueueNotificationsBuilderBelowThreshold() { + pubnub.configuration.requestMessageCountThreshold = 10 + + val gotStatus = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(false)) + } + + @Test + fun testQueueNotificationsBuilderThresholdMatched() { + pubnub.configuration.requestMessageCountThreshold = 1 + + val gotStatus = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "o": { + "t": "14737141991877032", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannel" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(true)) + } + + @Test + fun testQueueNotificationsBuilderThresholdExceeded() { + pubnub.configuration.requestMessageCountThreshold = 1 + + val gotStatus = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "m": [ + { + "a": "4", + "b": "coolChannel", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "f": 0, + "i": "Client-g5d4g", + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "o": { + "r": 2, + "t": "14737141991877032" + }, + "p": { + "r": 1, + "t": "14607577960925503" + } + }, + { + "a": "5", + "b": "coolChannel2", + "c": "coolChannel2", + "d": { + "text": "Message2" + }, + "f": 0, + "i": "Client-g5d4g", + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4g", + "o": { + "r": 2, + "t": "14737141991877033" + }, + "p": { + "r": 1, + "t": "14607577960925504" + } + } + ], + "t": { + "r": 1, + "t": "14607577960932487" + } + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(true)) + } + + @Test + fun testSubscribeBuilderWithAccessManager403Error() { + val gotStatus = AtomicInteger() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withStatus(403).withBody( + """ + { + "message": "Forbidden", + "payload": { + "channels": [ + "ch1", + "ch2" + ], + "channel-groups": [ + ":cg1", + ":cg2" + ] + }, + "error": true, + "service": "Access Manager", + "status": 403 + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNAccessDeniedCategory) { + assertEquals(PNStatusCategory.PNAccessDeniedCategory, pnStatus.category) + assertEquals(listOf("ch1", "ch2"), pnStatus.affectedChannels) + assertEquals(listOf("cg1", "cg2"), pnStatus.affectedChannelGroups) + gotStatus.addAndGet(1) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(1)) + } + + @Test + fun testNamingSubscribeChannelGroupBuilder() { + val gotStatus = AtomicBoolean() + val gotMessage = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChannelGroup" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + assertEquals(2, pnStatus.affectedChannels.size) + gotStatus.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size > 0) + assertEquals("Message", pubnub.mapper.elementToString(pnMessageResult.message, "text")) + assertEquals("coolChannel", pnMessageResult.channel) + assertEquals("coolChannelGroup", pnMessageResult.subscription) + gotMessage.set(true) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(gotMessage) + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(gotStatus) + } + + @Test + fun testPresenceSubscribeBuilder() { + val gotStatus = AtomicInteger() + val gotMessage = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14614512228786519", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14614512228418349", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel-pnpres", + "d": { + "action": "join", + "timestamp": 1461451222, + "uuid": "4a6d5df7-e301-4e73-a7b7-6af9ab484eb0", + "occupancy": 1 + }, + "b": "coolChannel-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + assertEquals("coolChannel", pnPresenceEventResult.channel) + assertEquals(null, pnPresenceEventResult.subscription) + gotMessage.set(true) + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotMessage, IsEqual.equalTo(true)) + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(1)) + } + + @Test + fun testPresenceChannelGroupSubscribeBuilder() { + val gotStatus = AtomicInteger() + val gotMessage = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14614512228786519", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14614512228418349", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel-pnpres", + "d": { + "action": "join", + "timestamp": 1461451222, + "uuid": "4a6d5df7-e301-4e73-a7b7-6af9ab484eb0", + "occupancy": 1 + }, + "b": "coolChannelGroup-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + assertEquals("coolChannel", pnPresenceEventResult.channel) + assertEquals("coolChannelGroup", pnPresenceEventResult.subscription) + gotMessage.set(true) + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotMessage, IsEqual.equalTo(true)) + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(gotStatus, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribeSlidingBuilder() { + val gotMessage1 = AtomicBoolean() + val gotMessage2 = AtomicBoolean() + val gotMessage3 = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "3", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("3")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "10", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message3" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("10")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "20", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Message10" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + when (pnMessageResult.message.asJsonObject["text"].asString) { + "Message" -> { + gotMessage1.set(true) + } + "Message3" -> { + gotMessage2.set(true) + } + "Message10" -> { + gotMessage3.set(true) + } + } + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic( + gotMessage1, + IsEqual.equalTo(true) + ) + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic( + gotMessage2, + IsEqual.equalTo(true) + ) + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic( + gotMessage3, + IsEqual.equalTo(true) + ) + } + + @Test + fun testSubscribeBuilderNumber() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": 10, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + assertEquals(10, pnMessageResult.message.asInt) + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribeBuilderWithMetadata() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14858178301085322", + "r": 7 + }, + "m": [ + { + "a": "4", + "f": 512, + "i": "02a7b822-220c-49b0-90c4-d9cbecc0fd85", + "s": 1, + "p": { + "t": "14858178301075219", + "r": 7 + }, + "k": "demo-36", + "c": "chTest", + "u": { + "status_update": { + "lat": 55.752023906250656, + "lon": 37.61749036080494, + "driver_id": 4722 + } + }, + "d": { + "City": "Goiania", + "Name": "Marcelo" + } + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + assertEquals( + """{"status_update":{"lat":55.752023906250656,"lon":37.61749036080494,"driver_id":4722}}""", + pnMessageResult.userMetadata.toString() + ) + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribeBuilderWithState() { + val subscribeHits = AtomicInteger(0) + val heartbeatHits = AtomicInteger(0) + val expectedPayload = PubNubUtil.urlDecode( + """%7B%22ch1%22%3A%5B%22p1%22%2C%22p2%22%5D%2C%22cg2%22%3A%5B%22p1%22%2C%22p2%22%5D%7D""" + ) + val expectedMap = pubnub.mapper.fromJson?>( + expectedPayload, + object : TypeToken?>() {}.type + ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + val heartbeatRequests = findAll( + getRequestedFor( + urlMatching( + """/v2/presence/sub-key/${pubnub.configuration.subscribeKey}/channel/ch2,ch1/heartbeat.*""" + ) + ) + ) + val subscribeRequests = findAll( + getRequestedFor( + urlMatching( + """/v2/subscribe/${pubnub.configuration.subscribeKey}/ch2,ch1/.*""" + ) + ) + ) + for (request in subscribeRequests) { + val stateString = PubNubUtil.urlDecode(request.queryParameter("state").firstValue()) + var actualMap: HashMap? = null + try { + actualMap = pubnub.mapper.fromJson( + stateString, + object : TypeToken?>() {}.type + ) + } catch (e: PubNubException) { + e.printStackTrace() + } + if (actualMap != null && actualMap == expectedMap) { + subscribeHits.getAndAdd(1) + } + } + for (request in heartbeatRequests) { + if (!request.queryParams.containsKey("state")) { + heartbeatHits.getAndAdd(1) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1", "cg2") + }.execute() + + pubnub.setPresenceState().apply { + channels = listOf("ch1") + channelGroups = listOf("cg2") + state = listOf("p1", "p2") + }.async { _, _ -> } + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until { subscribeHits.get() > 0 && heartbeatHits.get() > 0 } + } + + @Test + fun testSubscribeChannelGroupBuilder() { + val atomic = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + for (request in requests) { + val channelGroupQuery = request.queryParameter("channel-group") + if (channelGroupQuery != null && channelGroupQuery.firstValue() == "cg1,cg2") { + atomic.set(true) + } + } + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channelGroups = listOf("cg1", "cg2") + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilTrue(atomic) + } + + @Test + fun testSubscribeChannelGroupWithPresenceBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + for (request in requests) { + val channelGroups = request.queryParameter("channel-group") + .firstValue() + .split(",") + .toMutableList() + .sorted() + if ("cg1,cg1-pnpres,cg2,cg2-pnpres" == channelGroups.toCsv()) { + atomic.addAndGet(1) + } + } + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channelGroups = listOf("cg1", "cg2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribeWithFilterExpressionBuilder() { + val atomic = AtomicBoolean() + + pubnub.configuration.filterExpression = "much=filtering" + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Kotlin/.*")) + .withQueryParam("filter-expr", matching("much=filtering")) + .withQueryParam("tt", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = + findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size > 0) + atomic.set(true) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic) + } + + @Test + fun testSubscribeWithEncryption() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14718972508742569", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 512, + "i": "ff374d0b-b866-40db-9ced-42d205bb808b", + "p": { + "t": "14718972508739738", + "r": 1 + }, + "k": "demo-36", + "c": "max_ch1", + "d": "6QoqmS9CnB3W9+I4mhmL7w==" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.cipherKey = "hello" + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size > 0) + assertEquals("hey", pubnub.mapper.elementToString(pnMessageResult.message, "text")) + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribeWithEncryptionPNOther() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14718972508742569", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 512, + "i": "ff374d0b-b866-40db-9ced-42d205bb808b", + "p": { + "t": "14718972508739738", + "r": 1 + }, + "k": "demo-36", + "c": "max_ch1", + "d": { + "pn_other": "6QoqmS9CnB3W9+I4mhmL7w==" + } + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.cipherKey = "hello" + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size > 0) + assertEquals( + "hey", + pnMessageResult.message.asJsonObject["pn_other"].asJsonObject["text"].asString + ) + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribePresenceBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = + findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + assertTrue(requests.size >= 1) + assertEquals("""{"text":"Enter Message Here"}""", pnMessageResult.message.toString()) + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, Matchers.greaterThan(0)) + } + + @Test + fun testSubscribePresencePayloadHereNowRefreshDeltaBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14901247588021627", + "r": 2 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14901247587675704", + "r": 1 + }, + "k": "demo-36", + "c": "moon-interval-deltas-pnpres", + "d": { + "action": "interval", + "timestamp": 1490124758, + "occupancy": 2, + "here_now_refresh": true, + "join": [ + "2220E216-5A30-49AD-A89C-1E0B5AE26AD7", + "4262AE3F-3202-4487-BEE0-1A0D91307DEB" + ] + }, + "b": "moon-interval-deltas-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (atomic.get() == 0) { + assertEquals(true, pnPresenceEventResult.hereNowRefresh) + assertTrue(pnPresenceEventResult.occupancy!! == 2) + atomic.incrementAndGet() + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribePresencePayloadJoinDeltaBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14901247588021627", + "r": 2 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14901247587675704", + "r": 1 + }, + "k": "demo-36", + "c": "moon-interval-deltas-pnpres", + "d": { + "action": "interval", + "timestamp": 1490124758, + "occupancy": 2, + "join": [ + "2220E216-5A30-49AD-A89C-1E0B5AE26AD7", + "4262AE3F-3202-4487-BEE0-1A0D91307DEB" + ] + }, + "b": "moon-interval-deltas-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (atomic.get() == 0) { + val joinList: MutableList = ArrayList() + joinList.add("2220E216-5A30-49AD-A89C-1E0B5AE26AD7") + joinList.add("4262AE3F-3202-4487-BEE0-1A0D91307DEB") + assertEquals("interval", pnPresenceEventResult.event) + assertEquals(joinList, pnPresenceEventResult.join) + assertTrue(pnPresenceEventResult.occupancy!! == 2) + atomic.incrementAndGet() + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribePresencePayloadLeaveDeltaBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14901247588021627", + "r": 2 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14901247587675704", + "r": 1 + }, + "k": "demo-36", + "c": "moon-interval-deltas-pnpres", + "d": { + "action": "interval", + "timestamp": 1490124758, + "occupancy": 2, + "leave": [ + "2220E216-5A30-49AD-A89C-1E0B5AE26AD7", + "4262AE3F-3202-4487-BEE0-1A0D91307DEB" + ] + }, + "b": "moon-interval-deltas-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (atomic.get() == 0) { + val leaveList: MutableList = ArrayList() + leaveList.add("2220E216-5A30-49AD-A89C-1E0B5AE26AD7") + leaveList.add("4262AE3F-3202-4487-BEE0-1A0D91307DEB") + assertEquals("interval", pnPresenceEventResult.event) + assertEquals(leaveList, pnPresenceEventResult.leave) + assertTrue(pnPresenceEventResult.occupancy!! == 2) + atomic.incrementAndGet() + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribePresencePayloadTimeoutDeltaBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14901247588021627", + "r": 2 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14901247587675704", + "r": 1 + }, + "k": "demo-36", + "c": "moon-interval-deltas-pnpres", + "d": { + "action": "interval", + "timestamp": 1490124758, + "occupancy": 2, + "timeout": [ + "2220E216-5A30-49AD-A89C-1E0B5AE26AD7", + "4262AE3F-3202-4487-BEE0-1A0D91307DEB" + ] + }, + "b": "moon-interval-deltas-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (atomic.get() == 0) { + val timeoutList = listOf( + "2220E216-5A30-49AD-A89C-1E0B5AE26AD7", + "4262AE3F-3202-4487-BEE0-1A0D91307DEB" + ) + assertEquals("interval", pnPresenceEventResult.event) + assertEquals(timeoutList, pnPresenceEventResult.timeout) + assertTrue(pnPresenceEventResult.occupancy!! == 2) + atomic.incrementAndGet() + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribePresencePayloadBuilder() { + val atomic = AtomicInteger(0) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14614512228786519", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "p": { + "t": "14614512228418349", + "r": 2 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel-pnpres", + "d": { + "action": "join", + "timestamp": 1461451222, + "uuid": "4a6d5df7-e301-4e73-a7b7-6af9ab484eb0", + "occupancy": 1 + }, + "b": "coolChannel-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (atomic.get() == 0) { + assertEquals("join", pnPresenceEventResult.event) + assertEquals("4a6d5df7-e301-4e73-a7b7-6af9ab484eb0", pnPresenceEventResult.uuid) + assertTrue(pnPresenceEventResult.occupancy!! == 1) + assertTrue(pnPresenceEventResult.timestamp!! == 1461451222L) + atomic.incrementAndGet() + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testSubscribePresenceStateCallback() { + val atomic = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch10,ch10-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14637536741734954", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 512, + "p": { + "t": "14637536740940378", + "r": 1 + }, + "k": "demo-36", + "c": "ch10-pnpres", + "d": { + "action": "join", + "timestamp": 1463753674, + "uuid": "24c9bb19-1fcd-4c40-a6f1-522a8a1329ef", + "occupancy": 3 + }, + "b": "ch10-pnpres" + }, + { + "a": "4", + "f": 512, + "p": { + "t": "14637536741726901", + "r": 1 + }, + "k": "demo-36", + "c": "ch10-pnpres", + "d": { + "action": "state-change", + "timestamp": 1463753674, + "data": { + "state": "cool" + }, + "uuid": "24c9bb19-1fcd-4c40-a6f1-522a8a1329ef", + "occupancy": 3 + }, + "b": "ch10-pnpres" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (pnPresenceEventResult.event == "state-change") { + if (pnPresenceEventResult.state!!.asJsonObject.has("state") && + pnPresenceEventResult.state.asJsonObject.get("state").asString == "cool" + ) { + atomic.set(true) + } + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch10") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic( + atomic, IsEqual.equalTo(true) + ) + } + + @Test + fun testSubscribeRegionBuilder() { + val atomic = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 8 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) + if (requests.size > 1) { + assertEquals("8", requests[1].queryParameter("tr").firstValue()) + atomic.set(true) + } + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(true)) + } + + @Test + fun testRemoveListener() { + val atomic = AtomicInteger(0) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + atomic.addAndGet(1) + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + atomic.addAndGet(1) + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + atomic.addAndGet(1) + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + + pubnub.addListener(sub1) + pubnub.removeListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(0)) + } + + @Test + fun testUnsubscribe() { + val statusReceived = AtomicBoolean() + val messageReceived = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + pubnub.unsubscribe().apply { + channels = listOf("ch1") + }.execute() + } + val affectedChannels = pnStatus.affectedChannels + if (affectedChannels.size == 1 && pnStatus.operation == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels[0] == "ch1") { + statusReceived.set(true) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + val requests = findAll( + getRequestedFor( + urlMatching("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0.*") + ) + ) + if (requests.isNotEmpty()) { + messageReceived.set(true) + } + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic( + messageReceived, IsEqual.equalTo(true) + ) + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic( + statusReceived, IsEqual.equalTo(true) + ) + } + + @Test + fun testAllHeartbeats() { + val statusRecieved = AtomicBoolean() + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation && !pnStatus.error) { + statusRecieved.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(statusRecieved, IsEqual.equalTo(true)) + } + + @Test + fun testAllHeartbeatsViaPresence() { + val statusReceived = AtomicBoolean() + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation && !pnStatus.error) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + assertNotNull(sub1) + pubnub.addListener(sub1) + + pubnub.presence().apply { + channels = listOf("ch1", "ch2") + connected = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic( + statusReceived, IsEqual.equalTo(true) + ) + } + + @Test + fun testAllHeartbeatsLeaveViaPresence() { + val statusReceived = AtomicBoolean() + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNUnsubscribeOperation && !pnStatus.error) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + + pubnub.addListener(sub1) + + pubnub.presence().apply { + channels = listOf("ch1", "ch2") + connected = false + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(statusReceived, IsEqual.equalTo(true)) + } + + @Test + fun testSuccessOnFailureVerbosityHeartbeats() { + val statusReceived = AtomicBoolean() + + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.FAILURES + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(statusReceived, IsEqual.equalTo(true)) + } + + @Test + fun testFailedHeartbeats() { + val statusReceived = AtomicBoolean() + + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation && pnStatus.error) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(statusReceived, IsEqual.equalTo(true)) + } + + @Test + fun testSilencedHeartbeats() { + val statusReceived = AtomicBoolean() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.NONE + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [{ + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + } + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + }] + } + """.trimIndent() + ) + ) + ) + + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAtomic(statusReceived, IsEqual.equalTo(false)) + } + + @Test + fun testFailedNoneHeartbeats() { + val statusReceived = AtomicBoolean() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.NONE + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation != PNOperationType.PNHeartbeatOperation) { + statusReceived.set(true) + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(4, TimeUnit.SECONDS) + .untilTrue(statusReceived) + } + + @Test + fun testHeartbeatsDisabled() { + val subscribeSuccess = AtomicBoolean() + val heartbeatFail = AtomicBoolean() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.configuration.heartbeatNotificationOptions) + assertEquals(300, pubnub.configuration.presenceTimeout) + assertEquals(0, pubnub.configuration.heartbeatInterval) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) + .willReturn( + aResponse() + .withBody(""" + { + "t": { + "t": null, + "r": 12 + }, + "m": [] + } + """.trimIndent()) + .withStatus(200) + ) + ) + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(""" + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent()) + ) + ) + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (!pnStatus.error) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true) + } + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + heartbeatFail.set(true) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until { subscribeSuccess.get() && !heartbeatFail.get() } + } + + @Test + fun testHeartbeatsEnabled() { + val subscribeSuccess = AtomicBoolean() + val heartbeatSuccess = AtomicBoolean() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.configuration.heartbeatNotificationOptions) + assertEquals(300, pubnub.configuration.presenceTimeout) + assertEquals(0, pubnub.configuration.heartbeatInterval) + + pubnub.configuration.presenceTimeout = 20 + + assertEquals(20, pubnub.configuration.presenceTimeout) + assertEquals(9, pubnub.configuration.heartbeatInterval) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) + .willReturn( + aResponse() + .withBody(""" + { + "t": { + "t": null, + "r": 12 + }, + "m": [] + } + """.trimIndent()) + .withStatus(200) + ) + ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(""" + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent()) + ) + ) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (!pnStatus.error) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true) + } + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + heartbeatSuccess.set(true) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + }) + + pubnub.subscribe().apply { + channels = listOf("ch1") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until { subscribeSuccess.get() && heartbeatSuccess.get() } + } + + @Test + fun testMinimumPresenceValueNoInterval() { + pubnub.configuration.presenceTimeout = 10 + assertEquals(20, pubnub.configuration.presenceTimeout) + assertEquals(9, pubnub.configuration.heartbeatInterval) + } + + @Test + fun testMinimumPresenceValueWithInterval() { + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatInterval = 50 + assertEquals(20, pubnub.configuration.presenceTimeout) + assertEquals(50, pubnub.configuration.heartbeatInterval) + } + + @Test + fun testUnsubscribeAll() { + val statusReceived = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "14607577960932487", + "r": 1 + }, + "m": [ + { + "a": "4", + "f": 0, + "i": "Client-g5d4g", + "p": { + "t": "14607577960925503", + "r": 1 + }, + "k": "sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", + "c": "coolChannel", + "d": { + "text": "Enter Message Here" + }, + "b": "coolChan-bnel" + } + ] + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + val sub1: SubscribeCallback = object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + pubnub.unsubscribe().apply { + channels = listOf("ch1") + }.execute() + } + + val affectedChannels = pnStatus.affectedChannels + + if (affectedChannels.size == 1 && pnStatus.operation == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels[0] == "ch1") { + pubnub.unsubscribe().apply { + channels = listOf("ch2") + }.execute() + } + } + + if (affectedChannels.size == 1 && pnStatus.operation == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels[0] == "ch2") { + statusReceived.set(true) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} + } + + pubnub.addListener(sub1) + + pubnub.subscribe().apply { + channels = listOf("ch1", "ch2") + withPresence = true + }.execute() + + Awaitility.await() + .atMost(4, TimeUnit.SECONDS) + .untilTrue(statusReceived) + } +} \ No newline at end of file From c66b01b595ab72f4ded883fcb799d90032761c47 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:42:42 +0200 Subject: [PATCH 36/98] Refactor and expose new endpoints --- .../kotlin/com/pubnub/api/PNConfiguration.kt | 11 +- src/main/kotlin/com/pubnub/api/PubNub.kt | 136 ++++++++++++++++-- src/main/kotlin/com/pubnub/api/PubNubError.kt | 90 +++++++++++- .../kotlin/com/pubnub/api/PubNubException.kt | 3 +- src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 16 +++ 5 files changed, 231 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt index 8efa346be..5fc98f397 100644 --- a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt +++ b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt @@ -9,12 +9,11 @@ import okhttp3.ConnectionSpec import okhttp3.logging.HttpLoggingInterceptor import java.net.Proxy import java.net.ProxySelector -import java.util.UUID +import java.util.* import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509ExtendedTrustManager - class PNConfiguration { private companion object Constants { @@ -24,7 +23,6 @@ class PNConfiguration { private const val NON_SUBSCRIBE_REQUEST_TIMEOUT = 10 private const val SUBSCRIBE_TIMEOUT = 310 private const val CONNECT_TIMEOUT = 5 - private const val DEFAULT_BASE_PATH = "ps.pndsn.com" } lateinit var subscribeKey: String @@ -35,7 +33,7 @@ class PNConfiguration { var uuid: String = "pn-${UUID.randomUUID()}" - var origin = DEFAULT_BASE_PATH + lateinit var origin: String var secure = true var logVerbosity = PNLogVerbosity.NONE @@ -60,6 +58,8 @@ class PNConfiguration { NON_SUBSCRIBE_REQUEST_TIMEOUT var maximumMessagesCacheSize = DEFAULT_DEDUPE_SIZE + var cacheBusting = false + var suppressLeaveEvents = false var disableTokenManager = false lateinit var filterExpression: String @@ -70,7 +70,7 @@ class PNConfiguration { var requestMessageCountThreshold: Int? = null var googleAppEngineNetworking = false var startSubscriberThread = true - var dedupOnSubscribe = true + var dedupOnSubscribe = false var proxy: Proxy? = null var proxySelector: ProxySelector? = null @@ -87,6 +87,7 @@ class PNConfiguration { internal fun isCipherKeyValid() = ::cipherKey.isInitialized && !cipherKey.isBlank() internal fun isPublishKeyValid() = ::publishKey.isInitialized && !publishKey.isBlank() internal fun isSecretKeyValid() = ::secretKey.isInitialized && !secretKey.isBlank() + internal fun isOriginValid() = ::origin.isInitialized && !origin.isBlank() internal fun isFilterExpressionKeyValid(function: String.() -> Unit) { if (::filterExpression.isInitialized && !filterExpression.isBlank()) { function.invoke(filterExpression) diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt index 4df9bf1cd..3736edd3c 100644 --- a/src/main/kotlin/com/pubnub/api/PubNub.kt +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -1,36 +1,45 @@ package com.pubnub.api +import com.pubnub.api.builder.PresenceBuilder import com.pubnub.api.builder.SubscribeBuilder import com.pubnub.api.builder.UnsubscribeBuilder import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.endpoints.FetchMessages import com.pubnub.api.endpoints.History -import com.pubnub.api.endpoints.Publish +import com.pubnub.api.endpoints.MessageCounts import com.pubnub.api.endpoints.Time +import com.pubnub.api.endpoints.channel_groups.* +import com.pubnub.api.endpoints.message_actions.AddMessageAction +import com.pubnub.api.endpoints.message_actions.GetMessageActions +import com.pubnub.api.endpoints.message_actions.RemoveMessageAction +import com.pubnub.api.endpoints.objects_api.users.* import com.pubnub.api.endpoints.presence.GetState import com.pubnub.api.endpoints.presence.HereNow import com.pubnub.api.endpoints.presence.SetState import com.pubnub.api.endpoints.presence.WhereNow -import com.pubnub.api.managers.BasePathManager -import com.pubnub.api.managers.MapperManager -import com.pubnub.api.managers.PublishSequenceManager -import com.pubnub.api.managers.RetrofitManager -import com.pubnub.api.managers.SubscriptionManager -import com.pubnub.api.managers.TelemetryManager -import java.util.Date -import java.util.UUID +import com.pubnub.api.endpoints.pubsub.Publish +import com.pubnub.api.endpoints.pubsub.Signal +import com.pubnub.api.endpoints.push.AddChannelsToPush +import com.pubnub.api.endpoints.push.ListPushProvisions +import com.pubnub.api.endpoints.push.RemoveAllPushChannelsForDevice +import com.pubnub.api.endpoints.push.RemoveChannelsFromPush +import com.pubnub.api.managers.* +import com.pubnub.api.vendor.Crypto +import java.util.* class PubNub(val configuration: PNConfiguration) { private companion object Constants { private const val TIMESTAMP_DIVIDER = 1000 private const val SDK_VERSION = "0.0.1-canary" + private const val MAX_SEQUENCE = 65535; } - private val basePathManager = BasePathManager(this) + private val basePathManager = BasePathManager(configuration) internal val mapper = MapperManager() internal val retrofitManager = RetrofitManager(this) - internal val publishSequenceManager = PublishSequenceManager() + internal val publishSequenceManager = PublishSequenceManager(MAX_SEQUENCE) internal val telemetryManager = TelemetryManager() internal val subscriptionManager = SubscriptionManager(this) @@ -42,22 +51,123 @@ class PubNub(val configuration: PNConfiguration) { fun timestamp() = Date().time / TIMESTAMP_DIVIDER fun publish() = Publish(this) + fun fire() = Publish(this).apply { + shouldStore = false + replicate = false + } + + fun signal() = Signal(this) fun subscribe() = SubscribeBuilder(subscriptionManager) fun unsubscribe() = UnsubscribeBuilder(subscriptionManager) + fun presence() = PresenceBuilder(subscriptionManager) + + fun addPushNotificationsOnChannels() = AddChannelsToPush(this) + fun removePushNotificationsFromChannels() = RemoveChannelsFromPush(this) + fun removeAllPushNotificationsFromDeviceWithPushToken() = RemoveAllPushChannelsForDevice(this) + fun auditPushChannelProvisions() = ListPushProvisions(this) + fun history() = History(this) + fun messageCounts() = MessageCounts(this) + fun fetchMessages() = FetchMessages(this) fun hereNow() = HereNow(this) fun whereNow() = WhereNow(this) - fun setState() = SetState(this) - fun getState() = GetState(this) + fun setPresenceState() = SetState(this) + fun getPresenceState() = GetState(this) fun time() = Time(this) + fun addMessageAction() = AddMessageAction(this) + fun getMessageActions() = GetMessageActions(this) + fun removeMessageAction() = RemoveMessageAction(this) + + fun listAllChannelGroups() = ListAllChannelGroup(this) + fun listChannelsForChannelGroup() = AllChannelsChannelGroup(this) + fun addChannelsToChannelGroup() = AddChannelChannelGroup(this) + fun removeChannelsFromChannelGroup() = RemoveChannelChannelGroup(this) + fun deleteChannelGroup() = DeleteChannelGroup(this) + + fun getUsers() = GetUsers(this) + fun getUser() = GetUser(this) + fun createUser() = CreateUser(this) + fun updateUser() = UpdateUser(this) + fun deleteUser() = DeleteUser(this) fun addListener(listener: SubscribeCallback) { subscriptionManager.addListener(listener) } + fun removeListener(listener: SubscribeCallback) { + subscriptionManager.removeListener(listener) + } + + fun getSubscribedChannels() = subscriptionManager.getSubscribedChannels() + fun getSubscribedChannelGroups() = subscriptionManager.getSubscribedChannelGroups() + fun unsubscribeAll() = subscriptionManager.unsubscribeAll() + + // End Message Actions API + // public methods + + /** + * Perform Cryptographic decryption of an input string using cipher key provided by PNConfiguration + * + * @param inputString String to be encrypted + * @return String containing the encryption of inputString using cipherKey + */ + fun decrypt(inputString: String): String { + return decrypt(inputString, configuration.cipherKey) + } + + /** + * Perform Cryptographic decryption of an input string using the cipher key + * + * @param inputString String to be encrypted + * @param cipherKey cipher key to be used for encryption + * @return String containing the encryption of inputString using cipherKey + * @throws PubNubException throws exception in case of failed encryption + */ + fun decrypt(inputString: String, cipherKey: String): String { + return Crypto(cipherKey).decrypt(inputString) + } + + /** + * Perform Cryptographic encryption of an input string and the cipher key provided by PNConfiguration + * + * @param inputString String to be encrypted + * @return String containing the encryption of inputString using cipherKey + */ + fun encrypt(inputString: String): String { + return encrypt(inputString, configuration.cipherKey) + } + + /** + * Perform Cryptographic encryption of an input string and the cipher key. + * + * @param inputString String to be encrypted + * @param cipherKey cipher key to be used for encryption + * @return String containing the encryption of inputString using cipherKey + * @throws PubNubException throws exception in case of failed encryption + */ + @Throws(PubNubException::class) + fun encrypt(inputString: String, cipherKey: String): String { + return Crypto(cipherKey).encrypt(inputString) + } + + fun reconnect() { + subscriptionManager.reconnect() + } + + fun disconnect() { + subscriptionManager.disconnect() + } + fun destroy() { subscriptionManager.destroy() retrofitManager.destroy() } + fun forceDestroy() { + subscriptionManager.destroy(true) + retrofitManager.destroy(true) + telemetryManager.stopCleanUpTimer() + } + + } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/PubNubError.kt b/src/main/kotlin/com/pubnub/api/PubNubError.kt index 912bb9fc0..ddb78ed8c 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubError.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubError.kt @@ -7,6 +7,11 @@ enum class PubNubError(val code: Int, val message: String) { "Timeout Occurred" ), + CONNECT_EXCEPTION( + 102, + "Connect Exception. Please verify if network is reachable" + ), + JSON_ERROR( 121, "JSON Error while processing API response" @@ -22,6 +27,11 @@ enum class PubNubError(val code: Int, val message: String) { "PubNub Connection not set" ), + GROUP_MISSING( + 136, + "Group Missing" + ), + SUBSCRIBE_KEY_MISSING( 138, "Subscribe Key not configured" @@ -32,11 +42,6 @@ enum class PubNubError(val code: Int, val message: String) { "Publish Key not configured" ), - CONNECT_EXCEPTION( - 102, - "Connect Exception. Please verify if network is reachable" - ), - SUBSCRIBE_TIMEOUT( 130, "Subscribe Timeout" @@ -70,6 +75,81 @@ enum class PubNubError(val code: Int, val message: String) { CHANNEL_AND_GROUP_MISSING( 141, "Channel and Group Missing." + ), + + PUSH_TYPE_MISSING( + 143, + "Push Type Missing." + ), + + DEVICE_ID_MISSING( + 144, + "Device ID Missing" + ), + + TIMETOKEN_MISSING( + 145, + "Timetoken Missing." + ), + + CHANNELS_TIMETOKEN_MISMATCH( + 146, + "Channels and timetokens are not equal in size." + ), + + USER_MISSING( + 147, + "User is missing" + ), + + USER_ID_MISSING( + 148, + "User ID is missing" + ), + + USER_NAME_MISSING( + 149, + "User name is missing" + ), + + MESSAGE_ACTION_MISSING( + 158, + "Message action is missing." + ), + + MESSAGE_ACTION_TYPE_MISSING( + 159, + "Message action type is missing." + ), + + MESSAGE_ACTION_VALUE_MISSING( + 160, + "Message action value is missing." + ), + + MESSAGE_TIMETOKEN_MISSING( + 161, + "Message timetoken is missing." + ), + + MESSAGE_ACTION_TIMETOKEN_MISSING( + 162, + "Message action timetoken is missing." + ), + + PUSH_TOPIC_MISSING( + 164, + "Push notification topic is missing. Required only if push type is APNS2." + ), + + UNINITIALIZED_PROPERTY_ACCESS( + 998, + "Uninitialized Property Access" + ), + + X( + 999, + "X ERROR" ); override fun toString(): String { diff --git a/src/main/kotlin/com/pubnub/api/PubNubException.kt b/src/main/kotlin/com/pubnub/api/PubNubException.kt index d8536baf0..1bf62cf09 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubException.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubException.kt @@ -1,13 +1,12 @@ package com.pubnub.api -import com.google.gson.JsonElement import retrofit2.Call data class PubNubException( var errorMessage: String? = null, var pubnubError: PubNubError? = null, - var jso: JsonElement? = null, + var jso: String? = null, var statusCode: Int = 0, var affectedCall: Call<*>? = null ) : Exception(errorMessage) { diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt index a34663ae9..80b0583c0 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -1,6 +1,7 @@ package com.pubnub.api import java.io.UnsupportedEncodingException +import java.net.URLDecoder import java.net.URLEncoder class PubNubUtil { @@ -35,6 +36,21 @@ class PubNubUtil { string } } + + /** + * Returns decoded String + * + * @param stringToEncode , input string + * @return , decoded string + */ + fun urlDecode(stringToEncode: String?): String? { + return try { + URLDecoder.decode(stringToEncode, CHARSET) + } catch (e: UnsupportedEncodingException) { + null + } + } + } } From 380fbe020c7430ec1b28127e9819387cb468f34f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:43:02 +0200 Subject: [PATCH 37/98] Add tests for Presence APIs --- .../api/endpoints/presence/GetStateEndpointTest.kt | 6 +++--- .../api/endpoints/presence/HereNowEndpointTest.kt | 13 ++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt index 67dc6dd5e..9f7e0bd88 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt @@ -33,7 +33,7 @@ class GetStateEndpointTest : BaseTest() { ) ) - val result = pubnub.getState().apply { + val result = pubnub.getPresenceState().apply { channels = listOf("testChannel") uuid = "sampleUUID" }.sync()!! @@ -64,7 +64,7 @@ class GetStateEndpointTest : BaseTest() { ) ) - val result = pubnub.getState().apply { + val result = pubnub.getPresenceState().apply { channels = listOf("testChannel") }.sync()!! @@ -86,7 +86,7 @@ class GetStateEndpointTest : BaseTest() { ) try { - pubnub.getState().apply { + pubnub.getPresenceState().apply { channels = listOf("testChannel") uuid = "sampleUUID" }.sync() diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt index 9d2b1f148..c6e9c56fb 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt @@ -1,13 +1,6 @@ package com.pubnub.api.endpoints.presence -import com.github.tomakehurst.wiremock.client.WireMock.aResponse -import com.github.tomakehurst.wiremock.client.WireMock.anyUrl -import com.github.tomakehurst.wiremock.client.WireMock.findAll -import com.github.tomakehurst.wiremock.client.WireMock.get -import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor -import com.github.tomakehurst.wiremock.client.WireMock.stubFor -import com.github.tomakehurst.wiremock.client.WireMock.urlMatching -import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.BaseTest import com.pubnub.api.PubNubError.SUBSCRIBE_KEY_MISSING import com.pubnub.api.PubNubException @@ -15,9 +8,7 @@ import com.pubnub.api.enums.PNOperationType.PNHereNowOperation import com.pubnub.api.models.consumer.presence.PNHereNowResult import org.awaitility.Awaitility import org.hamcrest.core.IsEqual -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicInteger From f461c41c69441271d7a8f29bb5abb67b15a31780 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:44:10 +0200 Subject: [PATCH 38/98] Add utility methods for tests --- src/test/kotlin/com/pubnub/api/AppTest.kt | 11 ++--- src/test/kotlin/com/pubnub/api/BaseTest.kt | 56 ++++++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/AppTest.kt b/src/test/kotlin/com/pubnub/api/AppTest.kt index b7c57858f..3f561e5ac 100644 --- a/src/test/kotlin/com/pubnub/api/AppTest.kt +++ b/src/test/kotlin/com/pubnub/api/AppTest.kt @@ -4,6 +4,7 @@ import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNLogVerbosity import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNOperationType.PNTimeOperation +import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.enums.PNStatusCategory.PNUnknownCategory import com.pubnub.api.models.consumer.PNStatus @@ -16,17 +17,12 @@ import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult import org.awaitility.Awaitility import org.awaitility.Durations -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle import java.text.SimpleDateFormat -import java.util.Properties -import java.util.UUID +import java.util.* import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean @@ -205,4 +201,5 @@ class AppTest { includeState = false }.sync() } + } diff --git a/src/test/kotlin/com/pubnub/api/BaseTest.kt b/src/test/kotlin/com/pubnub/api/BaseTest.kt index 8428e9816..a2fec0b8d 100644 --- a/src/test/kotlin/com/pubnub/api/BaseTest.kt +++ b/src/test/kotlin/com/pubnub/api/BaseTest.kt @@ -1,38 +1,66 @@ package com.pubnub.api import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNStatus import org.awaitility.Awaitility import org.awaitility.Durations import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.Duration import java.util.concurrent.atomic.AtomicBoolean abstract class BaseTest { - private val wireMockServer = WireMockServer() + private val wireMockServer: WireMockServer = WireMockServer() - protected val pubnub = PubNub(PNConfiguration().apply { + protected var pubnub = PubNub(PNConfiguration().apply { subscribeKey = "mySubscribeKey" publishKey = "myPublishKey" uuid = "myUUID" origin = "localhost:8080" secure = false + logVerbosity = PNLogVerbosity.BODY }) @BeforeEach fun beforeEach() { + onBefore() wireMockServer.start() } @AfterEach fun afterEach() { wireMockServer.stop() + onAfter() } + open fun onBefore() { + + } + + open fun onAfter() { + + } +} + +interface IEndpointTest { + fun provideTelemetryParameterName(): String + fun providePnOperation(): PNOperationType + fun provideKeyMatrix(): Int + @Test + fun testTelemetryParameter() + fun provideWorkingCode(): Endpoint<*, *> } +val SUB = 0x001 +val PUB = 0x010 +val AUTH = 0x100 + private fun observe(success: AtomicBoolean) { Awaitility.await() .atMost(Durations.FIVE_SECONDS) @@ -41,6 +69,7 @@ private fun observe(success: AtomicBoolean) { } fun AtomicBoolean.listen(): AtomicBoolean { + this.set(false) observe(this) return this } @@ -56,14 +85,25 @@ fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { } fun PNStatus.printQueryParams() { - val map = this.clientRequest?.url()?.queryParameterNames() - ?.map { - print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") - } + this.clientRequest!!.url().queryParameterNames().map { + print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") + } } -fun assertPnException(pnStatus: PNStatus, pubNubError: PubNubError) { +fun assertPnException(expectedPubNubError: PubNubError, pnStatus: PNStatus) { Assertions.assertTrue(pnStatus.error) - Assertions.assertEquals(pubNubError, pnStatus.exception!!.pubnubError) + Assertions.assertEquals(expectedPubNubError, pnStatus.exception!!.pubnubError) } +fun assertPnException(expectedPubNubError: PubNubError, exception: Exception) { + exception as PubNubException + Assertions.assertEquals(expectedPubNubError, exception.pubnubError) +} + +fun PNStatus.param(param: String) = clientRequest!!.url().queryParameter(param) + +fun emptyJson() = WireMock.aResponse().withBody("{}") + +fun failTest(message: String? = null) { + Assertions.fail(message) +} \ No newline at end of file From 881685f0bb2bf94bae2caf8033398f72e18d8eb2 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:45:01 +0200 Subject: [PATCH 39/98] Add generic tests --- .../kotlin/com/pubnub/api/EndpointTest.kt | 114 ++++++++++++++++++ src/test/kotlin/com/pubnub/api/PubNubTest.kt | 81 +++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/EndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/PubNubTest.kt diff --git a/src/test/kotlin/com/pubnub/api/EndpointTest.kt b/src/test/kotlin/com/pubnub/api/EndpointTest.kt new file mode 100644 index 000000000..12559c282 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/EndpointTest.kt @@ -0,0 +1,114 @@ +package com.pubnub.api + +import com.pubnub.api.enums.PNOperationType +import okhttp3.Request +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + +class EndpointTest : BaseTest() { + + @Test + fun testDefaultInstanceParamSetting() { + assertTrue(pubnub.configuration.includeRequestIdentifier) + assertFalse(pubnub.configuration.includeInstanceIdentifier) + } + + // todo test other params + + @Test + fun testBaseParamsIncludeInstanceId() { + pubnub.configuration.includeInstanceIdentifier = true + + fakeEndpoint { + assertTrue(it.containsKey("instanceid")) + }.sync() + } + + @Test + fun testBaseParamsNoIncludeInstanceId() { + fakeEndpoint { + assertEquals("myUUID", it["uuid"]) + assertFalse(it.containsKey("instanceid")) + }.sync() + } + + @Test + fun testBaseParamsIncludeRequestId() { + fakeEndpoint { + assertEquals("myUUID", it["uuid"]) + assertTrue(it.containsKey("requestid")) + }.sync() + } + + @Test + fun testBaseParamsNoIncludeRequestId() { + pubnub.configuration.includeRequestIdentifier = false + + fakeEndpoint { + assertEquals("myUUID", it["uuid"]) + assertFalse(it.containsKey("requestid")) + }.sync() + } + + @Test + fun testBaseParamsPersistentRequestId() { + pubnub.configuration.includeInstanceIdentifier = true + + val instanceId1 = AtomicReference() + val instanceId2 = AtomicReference() + + fakeEndpoint { + instanceId1.set(it["instanceid"]) + }.sync() + + fakeEndpoint { + instanceId2.set(it["instanceid"]) + }.sync() + + assertEquals(pubnub.instanceId, instanceId1.get()) + assertEquals(instanceId1.get(), instanceId2.get()) + } + + @Test + fun testUuid() { + val expectedUuid = UUID.randomUUID().toString() + pubnub.configuration.uuid = expectedUuid + + fakeEndpoint { + println(it) + assertEquals(expectedUuid, it["uuid"]) + }.sync() + } + + private fun fakeEndpoint( + paramsCondition: (map: HashMap) -> Unit + ) = object : Endpoint(pubnub) { + + override fun doWork(queryParams: HashMap): Call { + paramsCondition.invoke(queryParams) + return fakeCall() + } + + override fun createResponse(input: Response) = this + override fun operationType() = PNOperationType.PNSubscribeOperation + override fun isSubKeyRequired() = false + override fun isPubKeyRequired() = false + override fun isAuthRequired() = false + } + + private fun fakeCall() = object : Call { + override fun enqueue(callback: Callback) {} + override fun isExecuted() = false + override fun clone(): Call = this + override fun isCanceled() = false + override fun cancel() {} + override fun execute(): Response = Response.success(null) + override fun request() = Request.Builder().build() + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/PubNubTest.kt b/src/test/kotlin/com/pubnub/api/PubNubTest.kt new file mode 100644 index 000000000..ac410367c --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/PubNubTest.kt @@ -0,0 +1,81 @@ +package com.pubnub.api + +import com.pubnub.api.enums.PNReconnectionPolicy +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class PubNubTest : BaseTest() { + + lateinit var config: PNConfiguration + + override fun onBefore() { + config = PNConfiguration().apply { + subscribeKey = "demo" + publishKey = "demo" + } + } + + override fun onAfter() { + pubnub.destroy() + } + + @Test + fun testCreateSuccess() { + pubnub = PubNub(config) + assertEquals(true, pubnub.configuration.secure) + assertEquals("https://ps.pndsn.com", pubnub.baseUrl()) + } + + @Test + @Throws(PubNubException::class) + fun testEncryptCustomKey() { + pubnub = PubNub(config) + assertEquals("iALQtn3PfIXe74CT/wrS7g==", pubnub.encrypt("test1", "cipherKey").trim()) + } + + @Test + @Throws(PubNubException::class) + fun testEncryptConfigurationKey() { + config.cipherKey = "cipherKey" + pubnub = PubNub(config) + assertEquals("iALQtn3PfIXe74CT/wrS7g==", pubnub.encrypt("test1").trim()) + } + + @Test + @Throws(PubNubException::class) + fun testDecryptCustomKey() { + pubnub = PubNub(config) + assertEquals("test1", pubnub.decrypt("iALQtn3PfIXe74CT/wrS7g==", "cipherKey").trim()) + } + + @Test + @Throws(PubNubException::class) + fun testDecryptConfigurationKey() { + config.cipherKey = "cipherKey" + pubnub = PubNub(config) + assertEquals("test1", pubnub.decrypt("iALQtn3PfIXe74CT/wrS7g==").trim()) + } + + @Test + fun testconfig() { + config.subscribeTimeout = 3000 + config.connectTimeout = 4000 + config.nonSubscribeRequestTimeout = 5000 + config.reconnectionPolicy = PNReconnectionPolicy.NONE + pubnub = PubNub(config) + assertEquals("https://ps.pndsn.com", pubnub.baseUrl()) + assertEquals(3000, config.subscribeTimeout) + assertEquals(4000, config.connectTimeout) + assertEquals(5000, config.nonSubscribeRequestTimeout) + } + + @Test + fun getVersionAndTimeStamp() { + pubnub = PubNub(config) + val version = pubnub.version + val timeStamp = pubnub.timestamp() + assertEquals("0.0.1-canary", version) + assertTrue(timeStamp > 0) + } + +} \ No newline at end of file From 9e606370cf2d369595d34a6c742b8ef1ffe3a388 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:45:36 +0200 Subject: [PATCH 40/98] Add tests for the Sequence Manager --- .../managers/PublishSequenceManagerTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt diff --git a/src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt b/src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt new file mode 100644 index 000000000..556f114cd --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt @@ -0,0 +1,19 @@ +package com.pubnub.api.managers + +import com.pubnub.api.BaseTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PublishSequenceManagerTest : BaseTest() { + + @Test + fun testSequenceManager() { + val publishSequenceManager = PublishSequenceManager(2) + + assertEquals(1, publishSequenceManager.nextSequence()) + assertEquals(2, publishSequenceManager.nextSequence()) + assertEquals(1, publishSequenceManager.nextSequence()) + assertEquals(2, publishSequenceManager.nextSequence()) + } + +} \ No newline at end of file From 8155ea4ebc0f1abea571b6c3c951507cab9669b7 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:46:26 +0200 Subject: [PATCH 41/98] Implement User Objects API --- .../endpoints/objects_api/users/CreateUser.kt | 50 ++++++++++++++++ .../endpoints/objects_api/users/DeleteUser.kt | 36 ++++++++++++ .../endpoints/objects_api/users/GetUser.kt | 44 ++++++++++++++ .../endpoints/objects_api/users/GetUsers.kt | 58 +++++++++++++++++++ .../endpoints/objects_api/users/UpdateUser.kt | 51 ++++++++++++++++ .../com/pubnub/api/enums/PNUserFields.kt | 15 +++++ .../pubnub/api/managers/TelemetryManager.kt | 19 +++--- .../pubnub/api/models/consumer/PNStatus.kt | 1 - .../models/consumer/objects_api/PNObject.kt | 23 ++++++++ .../consumer/objects_api/user/PNUser.kt | 21 +++++++ .../consumer/objects_api/user/PNUserResult.kt | 27 +++++++++ .../util/FilteringParamsProvider.kt | 7 +++ .../util/InclusionParamsProvider.kt | 9 +++ .../objects_api/util/ListingParamsProvider.kt | 9 +++ .../server/objects_api/EntityArrayEnvelope.kt | 9 +++ .../server/objects_api/EntityEnvelope.kt | 8 +++ .../com/pubnub/api/services/UserService.kt | 48 +++++++++++++++ 17 files changed, 427 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt create mode 100644 src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityEnvelope.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/UserService.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt new file mode 100644 index 000000000..d06ca4bcf --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt @@ -0,0 +1,50 @@ +package com.pubnub.api.endpoints.objects_api.users + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNUserFields +import com.pubnub.api.models.consumer.objects_api.user.PNCreateUserResult +import com.pubnub.api.models.consumer.objects_api.user.PNUser +import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class CreateUser(pubnub: PubNub) : Endpoint, PNCreateUserResult>(pubnub), + InclusionParamsProvider { + + lateinit var user: PNUser + + override var includeFields: List = emptyList() + + override fun validateParams() { + super.validateParams() + if (!::user.isInitialized) { + throw PubNubException(PubNubError.USER_MISSING) + } + if (user.id.isBlank()) { + throw PubNubException(PubNubError.USER_ID_MISSING) + } + if (user.name.isBlank()) { + throw PubNubException(PubNubError.USER_NAME_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } + + return pubnub.retrofitManager.userService + .createUser( + pubnub.configuration.subscribeKey, + user, + queryParams.encodeAuth() + ) + } + + override fun createResponse(input: Response>): PNCreateUserResult? { + return PNCreateUserResult(input.body()!!.data!!) + } + + override fun operationType() = PNOperationType.PNCreateUserOperation +} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt new file mode 100644 index 000000000..497b208af --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt @@ -0,0 +1,36 @@ +package com.pubnub.api.endpoints.objects_api.users + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.objects_api.user.PNDeleteUserResult +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class DeleteUser(pubnub: PubNub) : Endpoint(pubnub) { + + lateinit var userId: String + + override fun validateParams() { + super.validateParams() + if (!::userId.isInitialized || userId.isBlank()) { + throw PubNubException(PubNubError.USER_ID_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call { + + return pubnub.retrofitManager.userService + .deleteUser( + subKey = pubnub.configuration.subscribeKey, + userId = userId, + options = queryParams.encodeAuth() + ) + } + + override fun createResponse(input: Response): PNDeleteUserResult? { + return PNDeleteUserResult() + } + + override fun operationType() = PNOperationType.PNDeleteUserOperation +} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt new file mode 100644 index 000000000..195ab523e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt @@ -0,0 +1,44 @@ +package com.pubnub.api.endpoints.objects_api.users + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNUserFields +import com.pubnub.api.models.consumer.objects_api.user.PNGetUserResult +import com.pubnub.api.models.consumer.objects_api.user.PNUser +import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class GetUser(pubnub: PubNub) : Endpoint, PNGetUserResult>(pubnub), + InclusionParamsProvider { + + lateinit var userId: String + + override var includeFields: List = emptyList() + + override fun validateParams() { + super.validateParams() + if (!::userId.isInitialized || userId.isBlank()) { + throw PubNubException(PubNubError.USER_ID_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } + + return pubnub.retrofitManager.userService + .getUser( + pubnub.configuration.subscribeKey, + userId, + queryParams.encodeAuth() + ) + } + + override fun createResponse(input: Response>): PNGetUserResult? { + return PNGetUserResult(input.body()!!.data!!) + } + + override fun operationType() = PNOperationType.PNCreateUserOperation +} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt new file mode 100644 index 000000000..f6f20c88d --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt @@ -0,0 +1,58 @@ +package com.pubnub.api.endpoints.objects_api.users + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNUserFields +import com.pubnub.api.models.consumer.objects_api.user.PNGetUsersResult +import com.pubnub.api.models.consumer.objects_api.user.PNUser +import com.pubnub.api.models.consumer.objects_api.util.FilteringParamsProvider +import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider +import com.pubnub.api.models.consumer.objects_api.util.ListingParamsProvider +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class GetUsers(pubnub: PubNub) : Endpoint, PNGetUsersResult>(pubnub), + InclusionParamsProvider, + ListingParamsProvider, + FilteringParamsProvider { + + lateinit var userId: String + + override var includeFields: List = emptyList() + override var limit: Int? = null + override var start: String? = null + override var end: String? = null + override var withTotalCount: Boolean? = null + override var filter: String? = null + + override fun validateParams() { + super.validateParams() + if (!::userId.isInitialized || userId.isBlank()) { + throw PubNubException(PubNubError.USER_ID_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } + limit?.let { queryParams["limit"] = it.toString() } + start?.let { queryParams["start"] = it } + end?.let { queryParams["end"] = it } + withTotalCount?.let { queryParams["count"] = it.toString() } + filter?.let { queryParams["filter"] = it.pnUrlEncode() } + + return pubnub.retrofitManager.userService + .getUsers( + subKey = pubnub.configuration.subscribeKey, + options = queryParams.encodeAuth() + ) + } + + override fun createResponse(input: Response>): PNGetUsersResult? { + return PNGetUsersResult().create(input.body()!!) + } + + override fun operationType() = PNOperationType.PNGetUsersOperation + +} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt new file mode 100644 index 000000000..b62a8a584 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt @@ -0,0 +1,51 @@ +package com.pubnub.api.endpoints.objects_api.users + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNUserFields +import com.pubnub.api.models.consumer.objects_api.user.PNUpdateUserResult +import com.pubnub.api.models.consumer.objects_api.user.PNUser +import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class UpdateUser(pubnub: PubNub) : Endpoint, PNUpdateUserResult>(pubnub), + InclusionParamsProvider { + + lateinit var user: PNUser + + override var includeFields: List = emptyList() + + override fun validateParams() { + super.validateParams() + if (!::user.isInitialized) { + throw PubNubException(PubNubError.USER_MISSING) + } + if (user.id.isBlank()) { + throw PubNubException(PubNubError.USER_ID_MISSING) + } + if (user.name.isBlank()) { + throw PubNubException(PubNubError.USER_NAME_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call> { + includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } + + return pubnub.retrofitManager.userService + .updateUser( + pubnub.configuration.subscribeKey, + user.id, + user, + queryParams.encodeAuth() + ) + } + + override fun createResponse(input: Response>): PNUpdateUserResult? { + return PNUpdateUserResult(input.body()!!.data!!) + } + + override fun operationType() = PNOperationType.PNUpdateUserOperation +} diff --git a/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt b/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt new file mode 100644 index 000000000..4a0297fca --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt @@ -0,0 +1,15 @@ +package com.pubnub.api.enums + +import java.util.* + +enum class PNUserFields(s: String) { + CUSTOM("custom"); + + private val value: String = s + + override fun toString(): String { + return value.toLowerCase(Locale.US) + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt index 3486ec3ea..5598641e4 100644 --- a/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt @@ -3,12 +3,17 @@ package com.pubnub.api.managers import com.pubnub.api.enums.PNOperationType import java.math.RoundingMode import java.text.NumberFormat -import java.util.Date -import java.util.Locale -import java.util.Timer -import java.util.TimerTask - -class TelemetryManager { +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.MutableList +import kotlin.collections.forEach +import kotlin.collections.set +import kotlin.collections.toList + +internal class TelemetryManager { companion object { private const val MAX_FRACTION_DIGITS = 3 @@ -59,7 +64,7 @@ class TelemetryManager { } - private fun stopCleanUpTimer() { + internal fun stopCleanUpTimer() { this.timer?.cancel() } diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt index fa14136c3..6613a4b84 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNStatus.kt @@ -24,7 +24,6 @@ data class PNStatus( ) { - var executedEndpoint: Endpoint<*, *>? = null var clientRequest: Request? = null diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt new file mode 100644 index 000000000..96cd6b556 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt @@ -0,0 +1,23 @@ +package com.pubnub.api.models.consumer.objects_api + +open class PNObject(val id: String) { + + val created: String? = null + val updated: String? = null + val eTag: String? = null + + // todo custom interceptor + open var custom: Any? = null + + override fun toString(): String { + return "PNObject(" + + "id='$id', " + + "created=$created, " + + "updated=$updated, " + + "eTag=$eTag, " + + "custom=$custom" + + ")" + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt new file mode 100644 index 000000000..a03a1aa5d --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt @@ -0,0 +1,21 @@ +package com.pubnub.api.models.consumer.objects_api.user + +import com.pubnub.api.models.consumer.objects_api.PNObject + +class PNUser(id: String, var name: String) : PNObject(id) { + + var externalId: String? = null + var profileUrl: String? = null + var email: String? = null + + override fun toString(): String { + return "PNUser(" + + "name='$name', " + + "externalId=$externalId, " + + "profileUrl=$profileUrl, " + + "email=$email" + + ")" + + " ${super.toString()}" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt new file mode 100644 index 000000000..11a7ece83 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt @@ -0,0 +1,27 @@ +package com.pubnub.api.models.consumer.objects_api.user + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope + +class PNCreateUserResult internal constructor(val user: PNUser) + +class PNDeleteUserResult + +class PNGetUserResult internal constructor(val user: PNUser) + +class PNGetUsersResult : EntityArrayEnvelope() { + + fun create(envelope: EntityArrayEnvelope): PNGetUsersResult { + val result = PNGetUsersResult() + result.totalCount = envelope.totalCount + result.next = envelope.next + result.prev = envelope.prev + result.data = envelope.data + return result + } + + fun create(): PNGetUsersResult { + return PNGetUsersResult() + } +} + +class PNUpdateUserResult internal constructor(val user: PNUser) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt new file mode 100644 index 000000000..53255d75e --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.objects_api.util + +interface FilteringParamsProvider { + + var filter: String? + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt new file mode 100644 index 000000000..fbadaef9c --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt @@ -0,0 +1,9 @@ +package com.pubnub.api.models.consumer.objects_api.util + +import com.pubnub.api.Endpoint + +interface InclusionParamsProvider> { + + var includeFields: List + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt new file mode 100644 index 000000000..5a693f887 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt @@ -0,0 +1,9 @@ +package com.pubnub.api.models.consumer.objects_api.util + +interface ListingParamsProvider { + + var limit: Int? + var start: String? + var end: String? + var withTotalCount: Boolean? +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.kt new file mode 100644 index 000000000..63f832941 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.kt @@ -0,0 +1,9 @@ +package com.pubnub.api.models.server.objects_api + +open class EntityArrayEnvelope : EntityEnvelope>() { + + var totalCount: Int = 0 + var next: String? = null + var prev: String? = null + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityEnvelope.kt new file mode 100644 index 000000000..070bebb3a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/objects_api/EntityEnvelope.kt @@ -0,0 +1,8 @@ +package com.pubnub.api.models.server.objects_api + +open class EntityEnvelope { + var status = 0 + private set + var data: T? = null + internal set +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/UserService.kt b/src/main/kotlin/com/pubnub/api/services/UserService.kt new file mode 100644 index 000000000..d74fc58be --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/UserService.kt @@ -0,0 +1,48 @@ +package com.pubnub.api.services + +import com.pubnub.api.models.consumer.objects_api.user.PNUser +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope +import com.pubnub.api.models.server.objects_api.EntityEnvelope +import retrofit2.Call +import retrofit2.http.* + +interface UserService { + + @GET("v1/objects/{subKey}/users") + fun getUsers( + @Path("subKey") subKey: String, + @QueryMap(encoded = true) options: Map + ): Call> + + @GET("v1/objects/{subKey}/users/{userId}") + fun getUser( + @Path("subKey") subKey: String, + @Path("userId") userId: String, + @QueryMap(encoded = true) options: Map + ): Call> + + @POST("v1/objects/{subKey}/users") + @Headers("Content-Type: application/json; charset=UTF-8") + fun createUser( + @Path("subKey") subKey: String, + @Body body: Any, + @QueryMap(encoded = true) options: Map + ): Call> + + @PATCH("v1/objects/{subKey}/users/{userId}") + @Headers("Content-Type: application/json; charset=UTF-8") + fun updateUser( + @Path("subKey") subKey: String, + @Path("userId") userId: String, + @Body body: Any, + @QueryMap(encoded = true) options: Map + ): Call> + + @DELETE("v1/objects/{subKey}/users/{userId}") + fun deleteUser( + @Path("subKey") subKey: String, + @Path("userId") userId: String, + @QueryMap(encoded = true) options: Map + ): Call + +} \ No newline at end of file From 02052dd5214b36c27114a361f22cc421da9811ea Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 23 Apr 2020 21:46:45 +0200 Subject: [PATCH 42/98] Add tests for Publish APIs --- .../api/endpoints/pubsub/PublishTest.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt new file mode 100644 index 000000000..7c44e75f3 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt @@ -0,0 +1,48 @@ +package com.pubnub.api.endpoints.pubsub + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.BaseTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PublishTest : BaseTest() { + + @Test + fun testFireSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","14598111595318003"]""")) + ) + + pubnub.fire().apply { + channel = "coolChannel" + message = "hi" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("true", requests[0].queryParameter("norep").firstValue()) + assertEquals("0", requests[0].queryParameter("store").firstValue()) + } + + @Test + fun testNoRepSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","14598111595318003"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + replicate = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("true", requests[0].queryParameter("norep").firstValue()) + } + +} \ No newline at end of file From c0727afa5eda7bb05ba4645eca80ce9cc18f00db Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 24 Apr 2020 15:06:34 +0200 Subject: [PATCH 43/98] Implement queryparam feature to all endpoints --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 12 ++++-- .../kotlin/com/pubnub/api/EndpointTest.kt | 41 ++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index 57093ed5e..94a9db709 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -27,6 +27,8 @@ abstract class Endpoint(protected val pubnub: PubNub) { private lateinit var call: Call private var silenceFailures = false + var queryParam: Map = emptyMap() + fun sync(): Output? { validateParams() @@ -201,10 +203,12 @@ abstract class Endpoint(protected val pubnub: PubNub) { } private fun createBaseParams(): HashMap { - val map = hashMapOf( - "pnsdk" to "PubNub-Kotlin/${pubnub.version}", - "uuid" to pubnub.configuration.uuid - ) + val map = hashMapOf() + + map.putAll(queryParam) + + map["pnsdk"] = "PubNub-Kotlin/${pubnub.version}" + map["uuid"] = pubnub.configuration.uuid if (pubnub.configuration.includeInstanceIdentifier) { map["instanceid"] = pubnub.instanceId diff --git a/src/test/kotlin/com/pubnub/api/EndpointTest.kt b/src/test/kotlin/com/pubnub/api/EndpointTest.kt index 12559c282..a395a4a9e 100644 --- a/src/test/kotlin/com/pubnub/api/EndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/EndpointTest.kt @@ -8,7 +8,6 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.* -import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference class EndpointTest : BaseTest() { @@ -86,6 +85,46 @@ class EndpointTest : BaseTest() { }.sync() } + @Test + fun testQueryParam() { + fakeEndpoint { + println(it) + assertEquals("sf", it["city"]) + assertEquals(pubnub.configuration.uuid, it["uuid"]) + assertEquals(4, it.size) + assertTrue(it.contains("pnsdk")) + assertTrue(it.contains("requestid")) + assertTrue(it.contains("uuid")) + }.apply { + queryParam = mapOf( + "city" to "sf", + "uuid" to "overwritten" + ) + }.sync() + } + + @Test + fun testQueryParamEmpty() { + fakeEndpoint { + assertEquals(3, it.size) + assertTrue(it.contains("pnsdk")) + assertTrue(it.contains("requestid")) + assertTrue(it.contains("uuid")) + }.apply { + queryParam = emptyMap() + }.sync() + } + + @Test + fun testQueryParamMissing() { + fakeEndpoint { + assertEquals(3, it.size) + assertTrue(it.contains("pnsdk")) + assertTrue(it.contains("requestid")) + assertTrue(it.contains("uuid")) + }.sync() + } + private fun fakeEndpoint( paramsCondition: (map: HashMap) -> Unit ) = object : Endpoint(pubnub) { From 20985cb7b9f61e8a27e0f05480c7f7bfe9d373d7 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:11:07 +0200 Subject: [PATCH 44/98] Register common deserialization interceptors --- build.gradle | 2 ++ .../com/pubnub/api/managers/MapperManager.kt | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index df3e34d0d..a7de90320 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,8 @@ dependencies { testImplementation 'org.awaitility:awaitility-kotlin:4.0.1' implementation 'com.github.tomakehurst:wiremock:2.26.3' + + implementation group: 'org.json', name: 'json', version: '20190722' } test { diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt index 998aff05e..7f0540d14 100644 --- a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -1,8 +1,8 @@ package com.pubnub.api.managers import com.google.gson.* -import com.google.gson.internal.bind.TypeAdapters.* import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException @@ -13,7 +13,7 @@ import java.lang.reflect.Type class MapperManager { private val objectMapper: Gson - private val converterFactory: Converter.Factory + internal val converterFactory: Converter.Factory init { val booleanAsIntAdapter = object : TypeAdapter() { @@ -25,19 +25,21 @@ class MapperManager { } } - override fun read(_in: JsonReader?): Boolean? { - return when (_in?.peek()) { - BOOLEAN -> _in?.nextBoolean() - NUMBER -> _in?.nextInt() != 0 - STRING -> _in?.nextString()?.toBoolean() - else -> throw IllegalStateException("Expected BOOLEAN or NUMBER") + override fun read(_in: JsonReader): Boolean? { + val peek: JsonToken = _in.peek() + return when (peek) { + JsonToken.BOOLEAN -> _in.nextBoolean() + JsonToken.NUMBER -> _in.nextInt() != 0 + JsonToken.STRING -> java.lang.Boolean.parseBoolean(_in.nextString()) + else -> throw IllegalStateException("Expected BOOLEAN or NUMBER but was $peek") } } } objectMapper = GsonBuilder() - .registerTypeAdapter(Boolean::class.java, booleanAsIntAdapter) + .registerTypeAdapter(Boolean::class.javaObjectType, booleanAsIntAdapter) .registerTypeAdapter(Boolean::class.javaPrimitiveType, booleanAsIntAdapter) + .registerTypeAdapter(Boolean::class.java, booleanAsIntAdapter) .create() converterFactory = GsonConverterFactory.create(objectMapper) } From ffc5ad546e82322bf7b2dd4ccda372b53a734f1c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:12:22 +0200 Subject: [PATCH 45/98] Improve SubscribeCallback modularity --- .../com/pubnub/api/callbacks/SubscribeCallback.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt index fa89e9b7e..f267c4cc8 100644 --- a/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt +++ b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt @@ -13,17 +13,17 @@ import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult abstract class SubscribeCallback { abstract fun status(pubnub: PubNub, pnStatus: PNStatus) - abstract fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) + open fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - abstract fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) + open fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - abstract fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) + open fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - abstract fun user(pubnub: PubNub, pnUserResult: PNUserResult) + open fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - abstract fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) + open fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - abstract fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) + open fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - abstract fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) + open fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } \ No newline at end of file From 28135c878db46891656908c68017e4f28e8d9d9f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:14:39 +0200 Subject: [PATCH 46/98] Implement Grant endpoint --- src/main/kotlin/com/pubnub/api/PubNub.kt | 13 +- .../com/pubnub/api/endpoints/access/Grant.kt | 116 ++++++++++++++++++ .../PNAccessManagerGrantResults.kt | 31 +++++ .../AccessManagerGrantPayload.kt | 26 ++++ .../v3/GrantTokenRequestBody.kt | 4 + .../api/services/AccessManagerService.kt | 24 ++++ 6 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt create mode 100644 src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt index 3736edd3c..aaf1e7f4d 100644 --- a/src/main/kotlin/com/pubnub/api/PubNub.kt +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -4,15 +4,12 @@ import com.pubnub.api.builder.PresenceBuilder import com.pubnub.api.builder.SubscribeBuilder import com.pubnub.api.builder.UnsubscribeBuilder import com.pubnub.api.callbacks.SubscribeCallback -import com.pubnub.api.endpoints.FetchMessages -import com.pubnub.api.endpoints.History -import com.pubnub.api.endpoints.MessageCounts -import com.pubnub.api.endpoints.Time +import com.pubnub.api.endpoints.* +import com.pubnub.api.endpoints.access.Grant import com.pubnub.api.endpoints.channel_groups.* import com.pubnub.api.endpoints.message_actions.AddMessageAction import com.pubnub.api.endpoints.message_actions.GetMessageActions import com.pubnub.api.endpoints.message_actions.RemoveMessageAction -import com.pubnub.api.endpoints.objects_api.users.* import com.pubnub.api.endpoints.presence.GetState import com.pubnub.api.endpoints.presence.HereNow import com.pubnub.api.endpoints.presence.SetState @@ -84,11 +81,7 @@ class PubNub(val configuration: PNConfiguration) { fun removeChannelsFromChannelGroup() = RemoveChannelChannelGroup(this) fun deleteChannelGroup() = DeleteChannelGroup(this) - fun getUsers() = GetUsers(this) - fun getUser() = GetUser(this) - fun createUser() = CreateUser(this) - fun updateUser() = UpdateUser(this) - fun deleteUser() = DeleteUser(this) + fun grant() = Grant(this) fun addListener(listener: SubscribeCallback) { subscriptionManager.addListener(listener) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt b/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt new file mode 100644 index 000000000..ea224feb6 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt @@ -0,0 +1,116 @@ +package com.pubnub.api.endpoints.access + +import com.google.gson.JsonElement +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.models.server.access_manager.AccessManagerGrantPayload +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class Grant(pubnub: PubNub) : Endpoint, PNAccessManagerGrantResult>(pubnub) { + + var read = false + var write = false + var manage = false + var delete = false + var ttl: Int = -1 + + var authKeys = emptyList() + var channels = emptyList() + var channelGroups = emptyList() + + override fun validateParams() { + super.validateParams() + if (!pubnub.configuration.isSecretKeyValid()) { + throw PubNubException(PubNubError.SECRET_KEY_MISSING) + } + } + + override fun getAffectedChannels() = channels + override fun getAffectedChannelGroups() = channelGroups + + override fun doWork(queryParams: HashMap): Call> { + channels.run { + if (isNotEmpty()) queryParams["channel"] = toCsv() + } + channelGroups.run { + if (isNotEmpty()) queryParams["channel-group"] = toCsv() + } + authKeys.run { + if (isNotEmpty()) queryParams["auth"] = toCsv() + } + + if (ttl >= -1) { + queryParams["ttl"] = ttl.toString() + } + + queryParams["r"] = if (read) "1" else "0" + queryParams["w"] = if (write) "1" else "0" + queryParams["m"] = if (manage) "1" else "0" + queryParams["d"] = if (delete) "1" else "0" + + return pubnub.retrofitManager.accessManagerService + .grant( + subKey = pubnub.configuration.subscribeKey, + options = queryParams + ) + } + + override fun createResponse(input: Response>): PNAccessManagerGrantResult? { + val data = input.body()!!.payload!! + + val constructedChannels = mutableMapOf>() + val constructedGroups = mutableMapOf>() + + // we have a case of a singular channel. + data.channel?.let { + constructedChannels[it] = data.authKeys!! + } + + if (channelGroups.size == 1) { + constructedGroups[pubnub.mapper.elementToString(data.channelGroups)!!] = data.authKeys!! + } else if (channelGroups.size > 1) { + val it = pubnub.mapper.getObjectIterator(data.channelGroups!!) + while (it.hasNext()) { + val (k, v) = it.next() + constructedGroups[k] = createKeyMap(v) + } + } + + data.channels?.forEach { + constructedChannels[it.key] = data.channels[it.key]!!.authKeys!! + } + + return PNAccessManagerGrantResult( + level = data.level, + ttl = data.ttl, + subscribeKey = data.subscribeKey, + channels = constructedChannels, + channelGroups = constructedGroups + ) + } + + private fun createKeyMap(input: JsonElement): Map { + val result: MutableMap = + HashMap() + val it: Iterator> = + pubnub.mapper.getObjectIterator(input, "auths") + while (it.hasNext()) { + val keyMap = it.next() + val pnAccessManagerKeyData = PNAccessManagerKeyData() + pnAccessManagerKeyData.manageEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "m")) + pnAccessManagerKeyData.writeEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "w")) + pnAccessManagerKeyData.readEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "r")) + result[keyMap.key] = pnAccessManagerKeyData + } + return result + } + + override fun operationType() = PNOperationType.PNAccessManagerGrant + + override fun isAuthRequired() = false +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt new file mode 100644 index 000000000..430dee193 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt @@ -0,0 +1,31 @@ +package com.pubnub.api.models.consumer.access_manager + +import com.google.gson.annotations.SerializedName + +class PNAccessManagerGrantResult( + val level: String, + val ttl: Int, + val subscribeKey: String, + val channels: Map>, + val channelGroups: Map> +) + +class PNAccessManagerKeyData() { + + @SerializedName("r") + internal var readEnabled: Boolean = false + + @SerializedName("w") + internal var writeEnabled: Boolean = false + + @SerializedName("m") + internal var manageEnabled: Boolean = false + + @SerializedName("d") + internal var deleteEnabled: Boolean = false +} + +class PNAccessManagerKeysData { + @SerializedName("auths") + internal val authKeys: Map? = null +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt b/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt new file mode 100644 index 000000000..3531906b7 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt @@ -0,0 +1,26 @@ +package com.pubnub.api.models.server.access_manager + +import com.google.gson.JsonElement +import com.google.gson.annotations.SerializedName +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeysData + +class AccessManagerGrantPayload { + + internal lateinit var level: String + + internal var ttl = 0 + + @SerializedName("subscribe_key") + internal lateinit var subscribeKey: String + + internal val channels: Map? = null + + @SerializedName("channel-groups") + internal val channelGroups: JsonElement? = null + + @SerializedName("auths") + internal val authKeys: Map? = null + + internal val channel: String? = null +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt b/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt new file mode 100644 index 000000000..801607bbe --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt @@ -0,0 +1,4 @@ +package com.pubnub.api.models.server.access_manager.v3 + +class GrantTokenRequestBody { +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt b/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt new file mode 100644 index 000000000..76ec6e872 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt @@ -0,0 +1,24 @@ +package com.pubnub.api.services + +import com.google.gson.JsonObject +import com.pubnub.api.models.server.Envelope +import com.pubnub.api.models.server.access_manager.AccessManagerGrantPayload +import retrofit2.Call +import retrofit2.http.* + +interface AccessManagerService { + + @GET("/v2/auth/grant/sub-key/{subKey}") + fun grant( + @Path("subKey") subKey: String, + @QueryMap options: Map + ): Call> + + @POST("/v3/pam/{subKey}/grant") + fun grantToken( + @Path("subKey") subKey: String, + @Body body: Any, + @QueryMap options: Map + ): Call + +} \ No newline at end of file From 954f58424312f5ebaa299ba70eaeb6d5dc394d8a Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:17:10 +0200 Subject: [PATCH 47/98] Minor refactoring --- src/main/java/com/pubnub/api/vendor/Crypto.java | 2 +- .../pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt | 1 - .../pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt | 1 - .../api/endpoints/channel_groups/RemoveChannelChannelGroup.kt | 1 - src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt | 2 +- src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt | 3 +-- .../kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt | 2 +- 7 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/pubnub/api/vendor/Crypto.java b/src/main/java/com/pubnub/api/vendor/Crypto.java index ee277c8e7..c63f95f40 100644 --- a/src/main/java/com/pubnub/api/vendor/Crypto.java +++ b/src/main/java/com/pubnub/api/vendor/Crypto.java @@ -59,7 +59,7 @@ public static byte[] hexEncode(byte[] input) throws PubNubException { } } - private static PubNubException newCryptoError(int code, Exception exception) { + public static PubNubException newCryptoError(int code, Exception exception) { PubNubException pubNubException = new PubNubException(); pubNubException.setPubnubError(PubNubError.CRYPTO_ERROR); pubNubException.setErrorMessage(exception.getClass().getSimpleName() + " " + exception.getMessage() + " " + code); diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt index 9296f88eb..725cf4c57 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt @@ -6,7 +6,6 @@ import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult -import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt index 49afd4d9c..35d81405b 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt @@ -1,6 +1,5 @@ package com.pubnub.api.endpoints.channel_groups -import com.google.gson.Gson import com.pubnub.api.Endpoint import com.pubnub.api.PubNub import com.pubnub.api.enums.PNOperationType diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt index d3565246a..3f6ef65ab 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt @@ -2,7 +2,6 @@ package com.pubnub.api.endpoints.channel_groups import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult import com.pubnub.api.models.server.Envelope import retrofit2.Call diff --git a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt index 8f6e72344..848e8a8fa 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt @@ -84,7 +84,7 @@ class ReconnectionManager(val pubnub: PubNub) { private fun callTime() { pubnub.time() - .async { result, status -> + .async { _, status -> if (!status.error) { stopHeartbeatTimer() reconnectionCallback.onReconnection() diff --git a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt index 85e51f1b7..6a67467ee 100644 --- a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt @@ -16,8 +16,7 @@ import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.server.SubscribeMessage import com.pubnub.api.workers.SubscribeMessageWorker -import java.util.ArrayList -import java.util.Timer +import java.util.* import java.util.concurrent.LinkedBlockingQueue import kotlin.concurrent.timerTask diff --git a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt index beb2ba44a..4332f90c4 100644 --- a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt +++ b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt @@ -198,7 +198,7 @@ class SubscribeMessageWorker( return null } - if (pubnub.mapper.isJsonObject(input!!) && pubnub.mapper.hasField(input, "pn_other")) { + if (pubnub.mapper.isJsonObject(input) && pubnub.mapper.hasField(input, "pn_other")) { val objectNode = pubnub.mapper.getAsObject(input) pubnub.mapper.putOnObject(objectNode, "pn_other", outputObject) outputObject = objectNode From 0fc3cc44a1596fb89042ce7905acd1d91bdbd0b6 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:18:37 +0200 Subject: [PATCH 48/98] Implement global uniform URL and path encoding --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 34 ++++++++----- src/main/kotlin/com/pubnub/api/PubNub.kt | 13 +++-- src/main/kotlin/com/pubnub/api/PubNubError.kt | 9 +++- .../com/pubnub/api/endpoints/History.kt | 2 - .../message_actions/AddMessageAction.kt | 2 - .../message_actions/GetMessageActions.kt | 21 +++----- .../message_actions/RemoveMessageAction.kt | 10 ++-- .../pubnub/api/endpoints/presence/GetState.kt | 10 +--- .../api/endpoints/presence/Heartbeat.kt | 2 - .../pubnub/api/endpoints/presence/SetState.kt | 6 +-- .../pubnub/api/endpoints/pubsub/Publish.kt | 11 +++-- .../com/pubnub/api/endpoints/pubsub/Signal.kt | 9 ++-- .../pubnub/api/endpoints/pubsub/Subscribe.kt | 10 ++-- .../com/pubnub/api/services/HistoryService.kt | 9 ++++ .../api/services/MessageActionService.kt | 11 ++--- .../pubnub/api/services/PresenceService.kt | 4 +- .../com/pubnub/api/services/PublishService.kt | 6 +-- .../com/pubnub/api/services/SignalService.kt | 4 +- .../pubnub/api/services/SubscribeService.kt | 2 +- .../com/pubnub/api/services/UserService.kt | 48 ------------------- 20 files changed, 89 insertions(+), 134 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/services/UserService.kt diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index 94a9db709..f49298be4 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -104,17 +104,19 @@ abstract class Endpoint(protected val pubnub: PubNub) { else -> { val (errorString, errorJson) = extractErrorBody(response) - val exception = with(PubNubException(errorString)) { + val exception = PubNubException(PubNubError.HTTP_ERROR).apply { + errorMessage = errorString jso = errorJson.toString() statusCode = response.code() - this } + val pnStatusCategory = when (response.code()) { SERVER_RESPONSE_FORBIDDEN -> PNAccessDeniedCategory SERVER_RESPONSE_BAD_REQUEST -> PNBadRequestCategory SERVER_RESPONSE_NOT_FOUND -> PNNotFoundCategory else -> PNUnknownCategory } + callback.invoke( null, createStatusResponse( @@ -185,7 +187,7 @@ abstract class Endpoint(protected val pubnub: PubNub) { ) } - protected fun encodeParams(params: Map): Map { + /*protected fun encodeParams(params: Map): Map { val encodedParams = HashMap(params) if (encodedParams.containsKey("auth")) { encodedParams["auth"] = encodedParams["auth"]?.let { @@ -193,7 +195,7 @@ abstract class Endpoint(protected val pubnub: PubNub) { } } return encodedParams - } + }*/ protected fun appendInclusionParams(map: MutableMap, params: List>) { /*if (params.isEmpty()) { @@ -350,7 +352,7 @@ abstract class Endpoint(protected val pubnub: PubNub) { private fun checkAndCreateResponse(input: Response): Output? { try { return createResponse(input) - } catch (pubnubException: PubNubException) { + } catch (pubnubException: PubNubException ){ throw pubnubException.apply { statusCode = input.code() jso = pubnub.mapper.toJson(input.body()) @@ -363,6 +365,20 @@ abstract class Endpoint(protected val pubnub: PubNub) { statusCode = input.code() jso = pubnub.mapper.toJson(input.body()) } + } catch (e: IllegalStateException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } catch (e: IndexOutOfBoundsException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } } } @@ -391,11 +407,3 @@ abstract class Endpoint(protected val pubnub: PubNub) { protected open fun isAuthRequired() = true } -internal fun HashMap.encodeAuth(): HashMap { - this["auth"]?.let { - this["auth"] = it.pnUrlEncode() - } - return this -} - -internal fun String.pnUrlEncode() = PubNubUtil.urlEncode(this) diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt index aaf1e7f4d..6a44bb409 100644 --- a/src/main/kotlin/com/pubnub/api/PubNub.kt +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -28,7 +28,7 @@ class PubNub(val configuration: PNConfiguration) { private companion object Constants { private const val TIMESTAMP_DIVIDER = 1000 - private const val SDK_VERSION = "0.0.1-canary" + private const val SDK_VERSION = "0.0.2-dev" private const val MAX_SEQUENCE = 65535; } @@ -45,7 +45,7 @@ class PubNub(val configuration: PNConfiguration) { fun baseUrl() = basePathManager.basePath() fun requestId() = UUID.randomUUID().toString() - fun timestamp() = Date().time / TIMESTAMP_DIVIDER + fun timestamp() = (Date().time / TIMESTAMP_DIVIDER).toInt() fun publish() = Publish(this) fun fire() = Publish(this).apply { @@ -59,13 +59,18 @@ class PubNub(val configuration: PNConfiguration) { fun presence() = PresenceBuilder(subscriptionManager) fun addPushNotificationsOnChannels() = AddChannelsToPush(this) - fun removePushNotificationsFromChannels() = RemoveChannelsFromPush(this) - fun removeAllPushNotificationsFromDeviceWithPushToken() = RemoveAllPushChannelsForDevice(this) + fun removePushNotificationsFromChannels() = + RemoveChannelsFromPush(this) + + fun removeAllPushNotificationsFromDeviceWithPushToken() = + RemoveAllPushChannelsForDevice(this) + fun auditPushChannelProvisions() = ListPushProvisions(this) fun history() = History(this) fun messageCounts() = MessageCounts(this) fun fetchMessages() = FetchMessages(this) + fun deleteMessages() = DeleteMessages(this) fun hereNow() = HereNow(this) fun whereNow() = WhereNow(this) fun setPresenceState() = SetState(this) diff --git a/src/main/kotlin/com/pubnub/api/PubNubError.kt b/src/main/kotlin/com/pubnub/api/PubNubError.kt index ddb78ed8c..e2b00b341 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubError.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubError.kt @@ -12,6 +12,11 @@ enum class PubNubError(val code: Int, val message: String) { "Connect Exception. Please verify if network is reachable" ), + SECRET_KEY_MISSING( + 114, + "ULS configuration failed. Secret Key not configured" + ), + JSON_ERROR( 121, "JSON Error while processing API response" @@ -34,12 +39,12 @@ enum class PubNubError(val code: Int, val message: String) { SUBSCRIBE_KEY_MISSING( 138, - "Subscribe Key not configured" + "ULS configuration failed. Subscribe Key not configured." ), PUBLISH_KEY_MISSING( 139, - "Publish Key not configured" + "ULS configuration failed. Publish Key not configured." ), SUBSCRIBE_TIMEOUT( diff --git a/src/main/kotlin/com/pubnub/api/endpoints/History.kt b/src/main/kotlin/com/pubnub/api/endpoints/History.kt index 030712cbf..dba53aff7 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/History.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/History.kt @@ -125,8 +125,6 @@ class History(pubnub: PubNub) : Endpoint(pubnub) { pubnub.mapper.elementToString(message) } - println("InputtTextt $inputText") - val outputText = crypto.decrypt(inputText!!) var outputObject = pubnub.mapper.fromJson(outputText, JsonElement::class.java) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt index 85f1470d3..876e78e8a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/AddMessageAction.kt @@ -37,8 +37,6 @@ class AddMessageAction(pubnub: PubNub) : Endpoint): Call> { - queryParams.putAll(encodeParams(queryParams)) - val body = JsonObject() body.addProperty("type", messageAction.type) body.addProperty("value", messageAction.value) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt index 3b8548011..4ed9dc57b 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt @@ -1,6 +1,5 @@ package com.pubnub.api.endpoints.message_actions -import com.google.gson.JsonObject import com.pubnub.api.Endpoint import com.pubnub.api.PubNub import com.pubnub.api.PubNubError @@ -8,11 +7,13 @@ import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.server.objects_api.EntityEnvelope import retrofit2.Call import retrofit2.Response import java.util.* -class GetMessageActions(pubnub: PubNub) : Endpoint(pubnub) { +class GetMessageActions(pubnub: PubNub) : + Endpoint>, PNGetMessageActionsResult>(pubnub) { lateinit var channel: String var start: Long? = null @@ -28,16 +29,12 @@ class GetMessageActions(pubnub: PubNub) : Endpoint() - - override fun doWork(queryParams: HashMap): Call { + override fun doWork(queryParams: HashMap): Call>> { start?.let { queryParams["start"] = it.toString().toLowerCase() } end?.let { queryParams["end"] = it.toString().toLowerCase() } limit?.let { queryParams["limit"] = it.toString().toLowerCase() } - queryParams.putAll(encodeParams(queryParams)) - return pubnub.retrofitManager.messageActionService .getMessageActions( pubnub.configuration.subscribeKey, @@ -46,15 +43,9 @@ class GetMessageActions(pubnub: PubNub) : Endpoint): PNGetMessageActionsResult? { - val iterator = pubnub.mapper.getArrayIterator(input.body()!!, "data") - val list = mutableListOf() - while (iterator.hasNext()) { - val messageActionJson = iterator.next() - list.add(pubnub.mapper.convertValue(messageActionJson, PNMessageAction::class.java)) - } + override fun createResponse(input: Response>>): PNGetMessageActionsResult? { return PNGetMessageActionsResult().apply { - actions = list + actions = input.body()!!.data!! } } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt index 970628b13..f460361bf 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt @@ -6,11 +6,12 @@ import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult +import com.pubnub.api.models.server.objects_api.EntityEnvelope import retrofit2.Call import retrofit2.Response import java.util.* -class RemoveMessageAction(pubnub: PubNub) : Endpoint(pubnub) { +class RemoveMessageAction(pubnub: PubNub) : Endpoint, PNRemoveMessageActionResult>(pubnub) { lateinit var channel: String var messageTimetoken: Long? = null @@ -31,9 +32,7 @@ class RemoveMessageAction(pubnub: PubNub) : Endpoint): Call { - queryParams.putAll(encodeParams(queryParams)) - + override fun doWork(queryParams: HashMap): Call> { return pubnub.retrofitManager.messageActionService .deleteMessageAction( subKey = pubnub.configuration.subscribeKey, @@ -44,7 +43,8 @@ class RemoveMessageAction(pubnub: PubNub) : Endpoint): PNRemoveMessageActionResult? { + override fun createResponse(input: Response>): PNRemoveMessageActionResult? { + input.body()!!.data!! return PNRemoveMessageActionResult() } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt index 7b6c6a32b..efd85ce32 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt @@ -1,17 +1,13 @@ package com.pubnub.api.endpoints.presence import com.google.gson.JsonElement -import com.pubnub.api.Endpoint -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException +import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.presence.PNGetStateResult import com.pubnub.api.models.server.Envelope -import com.pubnub.api.toCsv import retrofit2.Call import retrofit2.Response -import java.util.HashMap +import java.util.* class GetState(pubnub: PubNub) : Endpoint, PNGetStateResult>(pubnub) { @@ -35,8 +31,6 @@ class GetState(pubnub: PubNub) : Endpoint, PNGetStateResul queryParams["channel-group"] = channelGroups.toCsv() } - queryParams.putAll(encodeParams(queryParams)) - return pubnub.retrofitManager.presenceService.getState( pubnub.configuration.subscribeKey, channels.toCsv(), diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt index 1b6afc0ca..ad21bf4fd 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt @@ -39,8 +39,6 @@ internal class Heartbeat( else "," - queryParams.putAll(encodeParams(queryParams)) - return pubnub.retrofitManager.presenceService.heartbeat( pubnub.configuration.subscribeKey, channelsCsv, diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt index e2c9e0d19..4ee35e2b5 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt @@ -46,9 +46,7 @@ class SetState(pubnub: PubNub) : if (channelGroups.isNotEmpty()) { queryParams["channel-group"] = channelGroups.toCsv() } - queryParams["state"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(state)) - - queryParams.putAll(encodeParams(queryParams)) + queryParams["state"] = pubnub.mapper.toJson(state) return pubnub.retrofitManager.presenceService.setState( pubnub.configuration.subscribeKey, @@ -60,7 +58,7 @@ class SetState(pubnub: PubNub) : override fun createResponse(input: Response>): PNSetStateResult? { return PNSetStateResult( - state = input.body()!!.payload + state = input.body()!!.payload!! ) } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt index a26130ef5..c746a607f 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt @@ -1,6 +1,9 @@ package com.pubnub.api.endpoints.pubsub -import com.pubnub.api.* +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult import com.pubnub.api.vendor.Crypto @@ -38,7 +41,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { var stringifiedMessage = pubnub.mapper.toJson(message) if (isMetaValid()) { - queryParams["meta"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(meta)) + queryParams["meta"] = pubnub.mapper.toJson(meta) } queryParams["store"] = if (shouldStore) "1" else "0" @@ -49,8 +52,6 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { queryParams["seqn"] = pubnub.publishSequenceManager.nextSequence().toString() - queryParams.putAll(encodeParams(queryParams)) - if (pubnub.configuration.isCipherKeyValid()) { stringifiedMessage = Crypto(pubnub.configuration.cipherKey) .encrypt(stringifiedMessage) @@ -78,7 +79,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { stringifiedMessage = "\"$stringifiedMessage\"" } - stringifiedMessage = PubNubUtil.urlEncode(stringifiedMessage) + stringifiedMessage = /*PubNubUtil.urlEncode*/(stringifiedMessage) return pubnub.retrofitManager.publishService.publish( pubnub.configuration.publishKey, diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt index c6f7d1d4e..87ec1ee67 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Signal.kt @@ -1,6 +1,9 @@ package com.pubnub.api.endpoints.pubsub -import com.pubnub.api.* +import com.pubnub.api.Endpoint +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult import retrofit2.Call @@ -27,13 +30,11 @@ class Signal(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { override fun getAffectedChannels() = listOf(channel) override fun doWork(queryParams: HashMap): Call> { - queryParams.putAll(encodeParams(queryParams)) - return pubnub.retrofitManager.signalService.signal( pubKey = pubnub.configuration.publishKey, subKey = pubnub.configuration.subscribeKey, channel = channel, - message = PubNubUtil.urlEncode(pubnub.mapper.toJson(message)), + message = pubnub.mapper.toJson(message), options = queryParams ) } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt index 1ba04a60a..5e5bf867e 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt @@ -9,8 +9,8 @@ import java.util.* class Subscribe(pubnub: PubNub) : Endpoint(pubnub) { - var channels = listOf() - var channelGroups = listOf() + var channels = emptyList() + var channelGroups = emptyList() var timetoken: Long? = null var region: String? = null var state: Any? = null @@ -33,7 +33,7 @@ class Subscribe(pubnub: PubNub) : Endpoint } if (!filterExpression.isNullOrBlank()) { - queryParams["filter-expr"] = PubNubUtil.urlEncode(filterExpression!!) + queryParams["filter-expr"] = filterExpression!! } timetoken?.let { @@ -47,11 +47,9 @@ class Subscribe(pubnub: PubNub) : Endpoint queryParams["heartbeat"] = pubnub.configuration.presenceTimeout.toString() state?.let { - queryParams["state"] = PubNubUtil.urlEncode(pubnub.mapper.toJson(it)) + queryParams["state"] = pubnub.mapper.toJson(it) } - queryParams.putAll(encodeParams(queryParams)) - return pubnub.retrofitManager.subscribeService.subscribe( pubnub.configuration.subscribeKey, channels.toCsv(), diff --git a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt index 3049eadc0..61a1d155f 100644 --- a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt +++ b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt @@ -1,8 +1,10 @@ package com.pubnub.api.services import com.google.gson.JsonElement +import com.pubnub.api.models.server.DeleteMessagesEnvelope import com.pubnub.api.models.server.FetchMessagesEnvelope import retrofit2.Call +import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.QueryMap @@ -16,6 +18,13 @@ interface HistoryService { @QueryMap options: Map ): Call + @DELETE("v3/history/sub-key/{subKey}/channel/{channels}") + fun deleteMessages( + @Path("subKey") subKey: String, + @Path("channels") channels: String, + @QueryMap options: Map + ): Call + @GET("v3/history/sub-key/{subKey}/channel/{channels}") fun fetchMessages( @Path("subKey") subKey: String, diff --git a/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt index 7acb13ee5..f5f20b3dd 100644 --- a/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt +++ b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt @@ -1,6 +1,5 @@ package com.pubnub.api.services -import com.google.gson.JsonObject import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.api.models.server.objects_api.EntityEnvelope import retrofit2.Call @@ -15,15 +14,15 @@ interface MessageActionService { @Path("channel") channel: String, @Path("messageTimetoken") messageTimetoken: String, @Body body: Any, - @QueryMap(encoded = true) options: Map + @QueryMap options: Map ): Call> @GET("v1/message-actions/{subKey}/channel/{channel}") fun getMessageActions( @Path("subKey") subKey: String, @Path("channel") channel: String, - @QueryMap(encoded = true) options: Map - ): Call + @QueryMap options: Map + ): Call>> @DELETE("v1/message-actions/{subKey}/channel/{channel}/message/{messageTimetoken}/action/{actionTimetoken}") fun deleteMessageAction( @@ -31,7 +30,7 @@ interface MessageActionService { @Path("channel") channel: String, @Path("messageTimetoken") messageTimetoken: String, @Path("actionTimetoken") actionTimetoken: String, - @QueryMap(encoded = true) options: Map - ): Call + @QueryMap options: Map + ): Call> } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt index b9be3e293..2f8096855 100644 --- a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt +++ b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt @@ -21,7 +21,7 @@ interface PresenceService { fun heartbeat( @Path("subKey") subKey: String, @Path("channel") channel: String, - @QueryMap(encoded = true) options: Map + @QueryMap options: Map ): Call> @GET("v2/presence/sub-key/{subKey}/uuid/{uuid}") @@ -57,7 +57,7 @@ interface PresenceService { @Path("subKey") subKey: String, @Path("channel") channel: String, @Path("uuid") uuid: String, - @QueryMap(encoded = true) options: Map + @QueryMap options: Map ): Call> } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PublishService.kt b/src/main/kotlin/com/pubnub/api/services/PublishService.kt index 5ecbe2f33..e53a0e3eb 100644 --- a/src/main/kotlin/com/pubnub/api/services/PublishService.kt +++ b/src/main/kotlin/com/pubnub/api/services/PublishService.kt @@ -10,8 +10,8 @@ interface PublishService { @Path("pubKey") pubKey: String, @Path("subKey") subKey: String, @Path("channel") channel: String, - @Path(value = "message", encoded = true) message: String, - @QueryMap(encoded = true) options: Map + @Path(value = "message", encoded = false) message: String, + @QueryMap(encoded = false) options: Map ): Call> @POST("publish/{pubKey}/{subKey}/0/{channel}/0") @@ -21,6 +21,6 @@ interface PublishService { @Path("subKey") subKey: String, @Path("channel") channel: String, @Body body: Any, - @QueryMap(encoded = true) options: Map + @QueryMap(encoded = false) options: Map ): Call> } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/SignalService.kt b/src/main/kotlin/com/pubnub/api/services/SignalService.kt index 20ad85ac3..a11d1134f 100644 --- a/src/main/kotlin/com/pubnub/api/services/SignalService.kt +++ b/src/main/kotlin/com/pubnub/api/services/SignalService.kt @@ -12,8 +12,8 @@ interface SignalService { @Path("pubKey") pubKey: String, @Path("subKey") subKey: String, @Path("channel") channel: String, - @Path(value = "payload", encoded = true) message: String, - @QueryMap(encoded = true) options: Map + @Path(value = "payload") message: String, + @QueryMap options: Map ): Call> } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt b/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt index 7eeb0672a..b95241165 100644 --- a/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt +++ b/src/main/kotlin/com/pubnub/api/services/SubscribeService.kt @@ -11,6 +11,6 @@ interface SubscribeService { fun subscribe( @Path("subKey") subKey: String, @Path("channel") channel: String, - @QueryMap(encoded = true) options: Map + @QueryMap options: Map ): Call } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/UserService.kt b/src/main/kotlin/com/pubnub/api/services/UserService.kt deleted file mode 100644 index d74fc58be..000000000 --- a/src/main/kotlin/com/pubnub/api/services/UserService.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.pubnub.api.services - -import com.pubnub.api.models.consumer.objects_api.user.PNUser -import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope -import com.pubnub.api.models.server.objects_api.EntityEnvelope -import retrofit2.Call -import retrofit2.http.* - -interface UserService { - - @GET("v1/objects/{subKey}/users") - fun getUsers( - @Path("subKey") subKey: String, - @QueryMap(encoded = true) options: Map - ): Call> - - @GET("v1/objects/{subKey}/users/{userId}") - fun getUser( - @Path("subKey") subKey: String, - @Path("userId") userId: String, - @QueryMap(encoded = true) options: Map - ): Call> - - @POST("v1/objects/{subKey}/users") - @Headers("Content-Type: application/json; charset=UTF-8") - fun createUser( - @Path("subKey") subKey: String, - @Body body: Any, - @QueryMap(encoded = true) options: Map - ): Call> - - @PATCH("v1/objects/{subKey}/users/{userId}") - @Headers("Content-Type: application/json; charset=UTF-8") - fun updateUser( - @Path("subKey") subKey: String, - @Path("userId") userId: String, - @Body body: Any, - @QueryMap(encoded = true) options: Map - ): Call> - - @DELETE("v1/objects/{subKey}/users/{userId}") - fun deleteUser( - @Path("subKey") subKey: String, - @Path("userId") userId: String, - @QueryMap(encoded = true) options: Map - ): Call - -} \ No newline at end of file From e0eaee9e84e61415625ed6baa946bf1b68284599 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:19:11 +0200 Subject: [PATCH 49/98] Remove Objects API implementations --- .../endpoints/objects_api/users/CreateUser.kt | 50 ---------------- .../endpoints/objects_api/users/DeleteUser.kt | 36 ------------ .../endpoints/objects_api/users/GetUser.kt | 44 -------------- .../endpoints/objects_api/users/GetUsers.kt | 58 ------------------- .../endpoints/objects_api/users/UpdateUser.kt | 51 ---------------- .../models/consumer/objects_api/PNObject.kt | 13 +++++ .../consumer/objects_api/user/PNUserResult.kt | 18 ++---- 7 files changed, 19 insertions(+), 251 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt delete mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt delete mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt delete mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt delete mode 100644 src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt deleted file mode 100644 index d06ca4bcf..000000000 --- a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/CreateUser.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.pubnub.api.endpoints.objects_api.users - -import com.pubnub.api.* -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.enums.PNUserFields -import com.pubnub.api.models.consumer.objects_api.user.PNCreateUserResult -import com.pubnub.api.models.consumer.objects_api.user.PNUser -import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider -import com.pubnub.api.models.server.objects_api.EntityEnvelope -import retrofit2.Call -import retrofit2.Response -import java.util.* - -class CreateUser(pubnub: PubNub) : Endpoint, PNCreateUserResult>(pubnub), - InclusionParamsProvider { - - lateinit var user: PNUser - - override var includeFields: List = emptyList() - - override fun validateParams() { - super.validateParams() - if (!::user.isInitialized) { - throw PubNubException(PubNubError.USER_MISSING) - } - if (user.id.isBlank()) { - throw PubNubException(PubNubError.USER_ID_MISSING) - } - if (user.name.isBlank()) { - throw PubNubException(PubNubError.USER_NAME_MISSING) - } - } - - override fun doWork(queryParams: HashMap): Call> { - includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } - - return pubnub.retrofitManager.userService - .createUser( - pubnub.configuration.subscribeKey, - user, - queryParams.encodeAuth() - ) - } - - override fun createResponse(input: Response>): PNCreateUserResult? { - return PNCreateUserResult(input.body()!!.data!!) - } - - override fun operationType() = PNOperationType.PNCreateUserOperation -} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt deleted file mode 100644 index 497b208af..000000000 --- a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/DeleteUser.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.pubnub.api.endpoints.objects_api.users - -import com.pubnub.api.* -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.models.consumer.objects_api.user.PNDeleteUserResult -import retrofit2.Call -import retrofit2.Response -import java.util.* - -class DeleteUser(pubnub: PubNub) : Endpoint(pubnub) { - - lateinit var userId: String - - override fun validateParams() { - super.validateParams() - if (!::userId.isInitialized || userId.isBlank()) { - throw PubNubException(PubNubError.USER_ID_MISSING) - } - } - - override fun doWork(queryParams: HashMap): Call { - - return pubnub.retrofitManager.userService - .deleteUser( - subKey = pubnub.configuration.subscribeKey, - userId = userId, - options = queryParams.encodeAuth() - ) - } - - override fun createResponse(input: Response): PNDeleteUserResult? { - return PNDeleteUserResult() - } - - override fun operationType() = PNOperationType.PNDeleteUserOperation -} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt deleted file mode 100644 index 195ab523e..000000000 --- a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUser.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.pubnub.api.endpoints.objects_api.users - -import com.pubnub.api.* -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.enums.PNUserFields -import com.pubnub.api.models.consumer.objects_api.user.PNGetUserResult -import com.pubnub.api.models.consumer.objects_api.user.PNUser -import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider -import com.pubnub.api.models.server.objects_api.EntityEnvelope -import retrofit2.Call -import retrofit2.Response -import java.util.* - -class GetUser(pubnub: PubNub) : Endpoint, PNGetUserResult>(pubnub), - InclusionParamsProvider { - - lateinit var userId: String - - override var includeFields: List = emptyList() - - override fun validateParams() { - super.validateParams() - if (!::userId.isInitialized || userId.isBlank()) { - throw PubNubException(PubNubError.USER_ID_MISSING) - } - } - - override fun doWork(queryParams: HashMap): Call> { - includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } - - return pubnub.retrofitManager.userService - .getUser( - pubnub.configuration.subscribeKey, - userId, - queryParams.encodeAuth() - ) - } - - override fun createResponse(input: Response>): PNGetUserResult? { - return PNGetUserResult(input.body()!!.data!!) - } - - override fun operationType() = PNOperationType.PNCreateUserOperation -} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt deleted file mode 100644 index f6f20c88d..000000000 --- a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/GetUsers.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.pubnub.api.endpoints.objects_api.users - -import com.pubnub.api.* -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.enums.PNUserFields -import com.pubnub.api.models.consumer.objects_api.user.PNGetUsersResult -import com.pubnub.api.models.consumer.objects_api.user.PNUser -import com.pubnub.api.models.consumer.objects_api.util.FilteringParamsProvider -import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider -import com.pubnub.api.models.consumer.objects_api.util.ListingParamsProvider -import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope -import retrofit2.Call -import retrofit2.Response -import java.util.* - -class GetUsers(pubnub: PubNub) : Endpoint, PNGetUsersResult>(pubnub), - InclusionParamsProvider, - ListingParamsProvider, - FilteringParamsProvider { - - lateinit var userId: String - - override var includeFields: List = emptyList() - override var limit: Int? = null - override var start: String? = null - override var end: String? = null - override var withTotalCount: Boolean? = null - override var filter: String? = null - - override fun validateParams() { - super.validateParams() - if (!::userId.isInitialized || userId.isBlank()) { - throw PubNubException(PubNubError.USER_ID_MISSING) - } - } - - override fun doWork(queryParams: HashMap): Call> { - includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } - limit?.let { queryParams["limit"] = it.toString() } - start?.let { queryParams["start"] = it } - end?.let { queryParams["end"] = it } - withTotalCount?.let { queryParams["count"] = it.toString() } - filter?.let { queryParams["filter"] = it.pnUrlEncode() } - - return pubnub.retrofitManager.userService - .getUsers( - subKey = pubnub.configuration.subscribeKey, - options = queryParams.encodeAuth() - ) - } - - override fun createResponse(input: Response>): PNGetUsersResult? { - return PNGetUsersResult().create(input.body()!!) - } - - override fun operationType() = PNOperationType.PNGetUsersOperation - -} diff --git a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt b/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt deleted file mode 100644 index b62a8a584..000000000 --- a/src/main/kotlin/com/pubnub/api/endpoints/objects_api/users/UpdateUser.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.pubnub.api.endpoints.objects_api.users - -import com.pubnub.api.* -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.enums.PNUserFields -import com.pubnub.api.models.consumer.objects_api.user.PNUpdateUserResult -import com.pubnub.api.models.consumer.objects_api.user.PNUser -import com.pubnub.api.models.consumer.objects_api.util.InclusionParamsProvider -import com.pubnub.api.models.server.objects_api.EntityEnvelope -import retrofit2.Call -import retrofit2.Response -import java.util.* - -class UpdateUser(pubnub: PubNub) : Endpoint, PNUpdateUserResult>(pubnub), - InclusionParamsProvider { - - lateinit var user: PNUser - - override var includeFields: List = emptyList() - - override fun validateParams() { - super.validateParams() - if (!::user.isInitialized) { - throw PubNubException(PubNubError.USER_MISSING) - } - if (user.id.isBlank()) { - throw PubNubException(PubNubError.USER_ID_MISSING) - } - if (user.name.isBlank()) { - throw PubNubException(PubNubError.USER_NAME_MISSING) - } - } - - override fun doWork(queryParams: HashMap): Call> { - includeFields.run { if (isNotEmpty()) queryParams["include"] = toCsv() } - - return pubnub.retrofitManager.userService - .updateUser( - pubnub.configuration.subscribeKey, - user.id, - user, - queryParams.encodeAuth() - ) - } - - override fun createResponse(input: Response>): PNUpdateUserResult? { - return PNUpdateUserResult(input.body()!!.data!!) - } - - override fun operationType() = PNOperationType.PNUpdateUserOperation -} diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt index 96cd6b556..425af780e 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt @@ -19,5 +19,18 @@ open class PNObject(val id: String) { ")" } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PNObject) return false + + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int { + return id.hashCode() + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt index 11a7ece83..9af768478 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt @@ -8,19 +8,13 @@ class PNDeleteUserResult class PNGetUserResult internal constructor(val user: PNUser) -class PNGetUsersResult : EntityArrayEnvelope() { +class PNGetUsersResult private constructor() : EntityArrayEnvelope() { - fun create(envelope: EntityArrayEnvelope): PNGetUsersResult { - val result = PNGetUsersResult() - result.totalCount = envelope.totalCount - result.next = envelope.next - result.prev = envelope.prev - result.data = envelope.data - return result - } - - fun create(): PNGetUsersResult { - return PNGetUsersResult() + internal constructor(envelope: EntityArrayEnvelope) : this() { + totalCount = envelope.totalCount + next = envelope.next + prev = envelope.prev + data = envelope.data } } From 23e91dfa93366d1eef7e61f61553065fecd3a218 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:19:34 +0200 Subject: [PATCH 50/98] Implement DeleteMessages --- .../pubnub/api/endpoints/DeleteMessages.kt | 47 +++++++++++++++++++ .../history/PNDeleteMessagesResult.kt | 3 ++ .../models/server/DeleteMessagesEnvelope.kt | 10 ++++ 3 files changed, 60 insertions(+) create mode 100644 src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.kt create mode 100644 src/main/kotlin/com/pubnub/api/models/server/DeleteMessagesEnvelope.kt diff --git a/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt b/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt new file mode 100644 index 000000000..49c336357 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt @@ -0,0 +1,47 @@ +package com.pubnub.api.endpoints + +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNDeleteMessagesResult +import com.pubnub.api.models.server.DeleteMessagesEnvelope +import retrofit2.Call +import retrofit2.Response +import java.util.* + +class DeleteMessages(pubnub: PubNub) : Endpoint(pubnub) { + + lateinit var channels: List + var start: Long? = null + var end: Long? = null + + override fun validateParams() { + super.validateParams() + if (!::channels.isInitialized || channels.isEmpty()) { + throw PubNubException(PubNubError.CHANNEL_MISSING) + } + } + + override fun doWork(queryParams: HashMap): Call { + start?.let { + queryParams["start"] = it.toString().toLowerCase(Locale.US) + } + end?.let { + queryParams["end"] = it.toString().toLowerCase(Locale.US) + } + + return pubnub.retrofitManager.historyService.deleteMessages( + pubnub.configuration.subscribeKey, + channels.toCsv(), + queryParams + ) + } + + override fun createResponse(input: Response): PNDeleteMessagesResult? { + if (input.body()!!.status != 200) { + throw PubNubException(PubNubError.PARSING_ERROR) + } + return PNDeleteMessagesResult() + } + + override fun operationType() = PNOperationType.PNDeleteMessagesOperation +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.kt new file mode 100644 index 000000000..886e29633 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.kt @@ -0,0 +1,3 @@ +package com.pubnub.api.models.consumer.history + +class PNDeleteMessagesResult \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/server/DeleteMessagesEnvelope.kt b/src/main/kotlin/com/pubnub/api/models/server/DeleteMessagesEnvelope.kt new file mode 100644 index 000000000..282653ba3 --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/models/server/DeleteMessagesEnvelope.kt @@ -0,0 +1,10 @@ +package com.pubnub.api.models.server + +import com.google.gson.annotations.SerializedName + +class DeleteMessagesEnvelope( + val status: Int?, + val error: Boolean?, + @SerializedName("error_message") + val errorMessage: String? +) \ No newline at end of file From 4aa4558e894ac3a61dc8bdf7ff7468b06f7c83a1 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:21:10 +0200 Subject: [PATCH 51/98] Implement request signing --- src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 183 ++++++++++++++++-- .../api/interceptor/SignatureInterceptor.kt | 17 ++ .../pubnub/api/managers/ListenerManager.kt | 1 - .../pubnub/api/managers/RetrofitManager.kt | 16 +- 4 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/com/pubnub/api/interceptor/SignatureInterceptor.kt diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt index 80b0583c0..30516a957 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -1,8 +1,19 @@ package com.pubnub.api +import com.pubnub.api.vendor.Base64 +import com.pubnub.api.vendor.Crypto +import okhttp3.Request +import okio.Buffer +import java.io.IOException import java.io.UnsupportedEncodingException import java.net.URLDecoder import java.net.URLEncoder +import java.nio.charset.Charset +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException +import java.util.* +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec class PubNubUtil { @@ -10,21 +21,6 @@ class PubNubUtil { private const val CHARSET = "UTF-8" - /** - * Returns encoded String - * - * @param stringToEncode , input string - * @return , encoded string - */ - fun urlEncode(stringToEncode: String): String { - return try { - URLEncoder.encode(stringToEncode, CHARSET).replace("+", "%20") - } catch (e: UnsupportedEncodingException) { - "" - } - - } - fun replaceLast(string: String, toReplace: String, replacement: String): String { val pos = string.lastIndexOf(toReplace) return if (pos > -1) { @@ -51,6 +47,163 @@ class PubNubUtil { } } + fun signRequest( + originalRequest: Request, + pnConfiguration: PNConfiguration, + timestamp: Int + ): Request { + // only sign if we have a secret key in place. + if (!pnConfiguration.isSecretKeyValid()) { + return originalRequest + } + val signature = generateSignature(pnConfiguration, originalRequest, timestamp) + val rebuiltUrl = originalRequest.url().newBuilder() + .addQueryParameter("timestamp", timestamp.toString()) + .addQueryParameter("signature", signature) + .build() + return originalRequest.newBuilder().url(rebuiltUrl).build() + } + + internal fun signSHA256(key: String, data: String): String { + val sha256HMAC: Mac + val hmacData: ByteArray + val secretKey = SecretKeySpec(key.toByteArray(charset(CHARSET)), "HmacSHA256") + sha256HMAC = try { + Mac.getInstance("HmacSHA256") + } catch (e: NoSuchAlgorithmException) { + throw Crypto.newCryptoError(0, e) + } + try { + sha256HMAC.init(secretKey) + } catch (e: InvalidKeyException) { + throw Crypto.newCryptoError(0, e) + } + hmacData = sha256HMAC.doFinal(data.toByteArray(charset(CHARSET))) + val signedd = String(Base64.encode(hmacData, 0), Charset.forName(CHARSET)) + .replace('+', '-') + .replace('/', '_') + .replace("\n", "") + return signedd + } + + private fun generateSignature( + configuration: PNConfiguration, + request: Request, + timestamp: Int + ): String? { + val isV2Signature: Boolean + val signatureBuilder = StringBuilder() + val requestURL = request.url().encodedPath() + val queryParams = mutableMapOf() + for (queryKey in request.url().queryParameterNames()) { + queryParams[queryKey] = request.url().queryParameter(queryKey)!! + // queryParams[queryKey] = request.url().encoded + } + queryParams["timestamp"] = timestamp.toString() + + // todo AB testing + val classic = true + val encodedQueryString = if (classic) { + preparePamArguments(queryParams) + } else { + preparePamArguments("${request.url().encodedQuery()}×tamp=${timestamp}") + } + + println("encodedQueryString $encodedQueryString") + + isV2Signature = !(requestURL.startsWith("/publish") && request.method().equals("post", ignoreCase = true)) + if (!isV2Signature) { + signatureBuilder.append(configuration.subscribeKey).append("\n") + signatureBuilder.append(configuration.publishKey).append("\n") + signatureBuilder.append(requestURL).append("\n") + signatureBuilder.append(encodedQueryString) + } else { + signatureBuilder.append(request.method().toUpperCase()).append("\n") + signatureBuilder.append(configuration.publishKey).append("\n") + signatureBuilder.append(requestURL).append("\n") + signatureBuilder.append(encodedQueryString).append("\n") + signatureBuilder.append(PubNubUtil.requestBodyToString(request)) + } + + var signature = "" + try { + signature = signSHA256(configuration.secretKey, signatureBuilder.toString()) + if (isV2Signature) { + signature = removeTrailingEqualSigns(signature) + signature = "v2.$signature" + } + } catch (e: PubNubException) { + println("signature failed on SignatureInterceptor: $e") + } catch (e: UnsupportedEncodingException) { + println("signature failed on SignatureInterceptor: $e") + } + return signature + } + + fun removeTrailingEqualSigns(signature: String): String { + var cleanSignature = signature + while (cleanSignature[cleanSignature.length - 1] == '=') { + cleanSignature = cleanSignature.substring(0, cleanSignature.length - 1) + } + return cleanSignature + } + + internal fun requestBodyToString(request: Request): String? { + if (request.body() == null) { + return "" + } + try { + val buffer = Buffer() + request.body()!!.writeTo(buffer) + return buffer.readUtf8() + } catch (e: IOException) { + e.printStackTrace() + } + return "" + } + + internal fun preparePamArguments(pamArgs: Map): String { + val pamKeys: Set = TreeSet(pamArgs.keys) + var stringifiedArguments = "" + var i = 0 + for (pamKey in pamKeys) { + if (i != 0) { + stringifiedArguments = "$stringifiedArguments&" + } + stringifiedArguments = stringifiedArguments + pamKey + "=" + pamEncode(pamArgs[pamKey]!!) + i += 1 + } + return stringifiedArguments + } + + internal fun preparePamArguments(encodedQueryString: String): String { + return encodedQueryString.split("&") + .toSortedSet() + .map { pamEncode(it, true) } + .joinToString("&") + } + + + /** + * Returns encoded String + * + * @param stringToEncode , input string + * @return , encoded string + */ + internal fun pamEncode(stringToEncode: String, alreadyPercentEncoded: Boolean = false): String { + /* !'()*~ */ + + return if (alreadyPercentEncoded) { + stringToEncode + } else { + URLEncoder.encode(stringToEncode, "UTF-8") + .replace("+", "%20") + }.run { + replace("*", "%2A") + // this + } + } + } } diff --git a/src/main/kotlin/com/pubnub/api/interceptor/SignatureInterceptor.kt b/src/main/kotlin/com/pubnub/api/interceptor/SignatureInterceptor.kt new file mode 100644 index 000000000..9ee09ba6a --- /dev/null +++ b/src/main/kotlin/com/pubnub/api/interceptor/SignatureInterceptor.kt @@ -0,0 +1,17 @@ +package com.pubnub.api.interceptor + +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubUtil +import okhttp3.Interceptor +import okhttp3.Response + +class SignatureInterceptor(val pubnub: PubNub) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + val request = PubNubUtil.signRequest(originalRequest, pubnub.configuration, pubnub.timestamp()) + return chain.proceed(request) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt index f303c7e93..8aa040aac 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt @@ -3,7 +3,6 @@ package com.pubnub.api.managers import com.pubnub.api.PubNub import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.models.consumer.PNStatus -import com.pubnub.api.models.consumer.message_actions.PNMessageAction import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult diff --git a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt index 9e36a6218..77d87ffe1 100644 --- a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt @@ -2,14 +2,13 @@ package com.pubnub.api.managers import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.interceptor.SignatureInterceptor import com.pubnub.api.services.* import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit - class RetrofitManager(val pubnub: PubNub) { private val transactionClientInstance: OkHttpClient by lazy { @@ -20,6 +19,8 @@ class RetrofitManager(val pubnub: PubNub) { createOkHttpClient(pubnub.configuration.subscribeTimeout) } + private val signatureInterceptor: SignatureInterceptor + internal val timeService: TimeService internal val publishService: PublishService internal val historyService: HistoryService @@ -28,12 +29,14 @@ class RetrofitManager(val pubnub: PubNub) { internal val signalService: SignalService internal val channelGroupService: ChannelGroupService internal val pushService: PushService - internal val userService: UserService + internal val accessManagerService: AccessManagerService internal val subscribeService: SubscribeService init { + signatureInterceptor = SignatureInterceptor(pubnub) + val transactionInstance = createRetrofit(transactionClientInstance) val subscriptionInstance = createRetrofit(subscriptionClientInstance) @@ -45,13 +48,14 @@ class RetrofitManager(val pubnub: PubNub) { signalService = transactionInstance.create(SignalService::class.java) channelGroupService = transactionInstance.create(ChannelGroupService::class.java) pushService = transactionInstance.create(PushService::class.java) - userService = transactionInstance.create(UserService::class.java) + accessManagerService = transactionInstance.create(AccessManagerService::class.java) subscribeService = subscriptionInstance.create(SubscribeService::class.java) } private fun createOkHttpClient(readTimeout: Int): OkHttpClient { val okHttpBuilder = OkHttpClient.Builder() + .retryOnConnectionFailure(true) .readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) .connectTimeout(pubnub.configuration.connectTimeout.toLong(), TimeUnit.SECONDS) @@ -75,6 +79,8 @@ class RetrofitManager(val pubnub: PubNub) { certificatePinner?.let { okHttpBuilder.certificatePinner(it) } } + okHttpBuilder.addInterceptor(signatureInterceptor) + val okHttpClient = okHttpBuilder.build() pubnub.configuration.maximumConnections?.let { okHttpClient.dispatcher().maxRequestsPerHost = it } @@ -86,7 +92,7 @@ class RetrofitManager(val pubnub: PubNub) { val retrofitBuilder = Retrofit.Builder() .client(okHttpClient) .baseUrl(pubnub.baseUrl()) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(pubnub.mapper.converterFactory) return retrofitBuilder.build() } From 58a1dcecddf512d6c7a99724e5358a534ed34d94 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:21:37 +0200 Subject: [PATCH 52/98] Remove Objects API implementations --- .../kotlin/com/pubnub/api/enums/PNUserFields.kt | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt diff --git a/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt b/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt deleted file mode 100644 index 4a0297fca..000000000 --- a/src/main/kotlin/com/pubnub/api/enums/PNUserFields.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.pubnub.api.enums - -import java.util.* - -enum class PNUserFields(s: String) { - CUSTOM("custom"); - - private val value: String = s - - override fun toString(): String { - return value.toLowerCase(Locale.US) - - } - -} \ No newline at end of file From 19970fe6765c734f2bdf40781492d1dd9223ffd9 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:22:59 +0200 Subject: [PATCH 53/98] Minor refactoring --- .../message_actions/PNMessageAction.kt | 18 ++++++++++-------- .../message_actions/PNMessageActionResult.kt | 4 +--- .../consumer/pubsub/objects/ObjectResult.kt | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt index 4b80d022c..80d20496b 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNMessageAction.kt @@ -1,19 +1,21 @@ package com.pubnub.api.models.consumer.message_actions -open class PNMessageAction { - val type: String - val value: String +open class PNMessageAction( + val type: String, + val value: String, val messageTimetoken: Long +) { - constructor(pnMessageAction: PNMessageAction) { - this.type = pnMessageAction.type - this.value = pnMessageAction.value - this.messageTimetoken = pnMessageAction.messageTimetoken + internal constructor(pnMessageAction: PNMessageAction) : this( + pnMessageAction.type, + pnMessageAction.value, + pnMessageAction.messageTimetoken + ) { this.uuid = pnMessageAction.uuid this.actionTimetoken = pnMessageAction.actionTimetoken } - var uuid: String + var uuid: String? = null private set var actionTimetoken: Long? = null private set diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt index 81d043cac..b7d722786 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt @@ -10,7 +10,5 @@ class PNMessageActionResult( data: PNMessageAction ) : ObjectResult(result, event, data) { - fun messageAction(): PNMessageAction { - return data - } + val messageAction = data } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt index 37dc1967f..1c5b395cf 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.kt @@ -3,7 +3,7 @@ package com.pubnub.api.models.consumer.pubsub.objects import com.pubnub.api.models.consumer.pubsub.BasePubSubResult open class ObjectResult( - open val result: BasePubSubResult, - open val event: String, - open val data: T + internal open val result: BasePubSubResult, + internal open val event: String, + internal open val data: T ) : BasePubSubResult(result) \ No newline at end of file From 420ea0e516917ecede77a2f62ecd5c7b03faeefa Mon Sep 17 00:00:00 2001 From: azurqsd Date: Sat, 23 May 2020 19:24:30 +0200 Subject: [PATCH 54/98] Remove Objects API implementations --- .../models/consumer/objects_api/PNObject.kt | 36 ------------------- .../consumer/objects_api/user/PNUser.kt | 21 ----------- .../consumer/objects_api/user/PNUserResult.kt | 21 ----------- .../util/FilteringParamsProvider.kt | 7 ---- .../util/InclusionParamsProvider.kt | 9 ----- .../objects_api/util/ListingParamsProvider.kt | 9 ----- 6 files changed, 103 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt deleted file mode 100644 index 425af780e..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/PNObject.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api - -open class PNObject(val id: String) { - - val created: String? = null - val updated: String? = null - val eTag: String? = null - - // todo custom interceptor - open var custom: Any? = null - - override fun toString(): String { - return "PNObject(" + - "id='$id', " + - "created=$created, " + - "updated=$updated, " + - "eTag=$eTag, " + - "custom=$custom" + - ")" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is PNObject) return false - - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - return id.hashCode() - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt deleted file mode 100644 index a03a1aa5d..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUser.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api.user - -import com.pubnub.api.models.consumer.objects_api.PNObject - -class PNUser(id: String, var name: String) : PNObject(id) { - - var externalId: String? = null - var profileUrl: String? = null - var email: String? = null - - override fun toString(): String { - return "PNUser(" + - "name='$name', " + - "externalId=$externalId, " + - "profileUrl=$profileUrl, " + - "email=$email" + - ")" + - " ${super.toString()}" - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt deleted file mode 100644 index 9af768478..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/user/PNUserResult.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api.user - -import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope - -class PNCreateUserResult internal constructor(val user: PNUser) - -class PNDeleteUserResult - -class PNGetUserResult internal constructor(val user: PNUser) - -class PNGetUsersResult private constructor() : EntityArrayEnvelope() { - - internal constructor(envelope: EntityArrayEnvelope) : this() { - totalCount = envelope.totalCount - next = envelope.next - prev = envelope.prev - data = envelope.data - } -} - -class PNUpdateUserResult internal constructor(val user: PNUser) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt deleted file mode 100644 index 53255d75e..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/FilteringParamsProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api.util - -interface FilteringParamsProvider { - - var filter: String? - -} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt deleted file mode 100644 index fbadaef9c..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/InclusionParamsProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api.util - -import com.pubnub.api.Endpoint - -interface InclusionParamsProvider> { - - var includeFields: List - -} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt b/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt deleted file mode 100644 index 5a693f887..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/objects_api/util/ListingParamsProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.pubnub.api.models.consumer.objects_api.util - -interface ListingParamsProvider { - - var limit: Int? - var start: String? - var end: String? - var withTotalCount: Boolean? -} \ No newline at end of file From 230871ae6ea1dad97a6c8c7f366d3c5c05b69b8e Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:38:53 +0200 Subject: [PATCH 55/98] Refactor and restructure legacy unit tests --- .../presence/GetStateEndpointTest.kt | 100 - .../api/endpoints/pubsub/PublishTest.kt | 48 - .../kotlin/com/pubnub/api/legacy/BaseTest.kt | 44 + .../com/pubnub/api/{ => legacy}/PubNubTest.kt | 10 +- .../endpoints/DeleteMessagesEndpointTest.kt | 139 ++ .../{ => legacy/endpoints}/EndpointTest.kt | 4 +- .../legacy/endpoints/HeartbeatEndpointTest.kt | 249 +++ .../endpoints/access/GrantEndpointTest.kt | 1739 +++++++++++++++++ .../AddChannelChannelGroupEndpointTest.kt | 3 +- .../AllChannelsChannelGroupEndpointTest.kt | 3 +- .../DeleteChannelGroupEndpointTest.kt | 15 +- .../ListAllChannelGroupEndpointTest.kt | 64 +- .../RemoveChannelChannelGroupEndpointTest.kt | 54 +- .../history/FetchMessagesEndpointTest.kt | 4 +- .../endpoints/history/HistoryEndpointTest.kt | 3 +- .../endpoints/history/MessageCountTest.kt | 106 +- .../AddMessageActionEndpointTest.kt | 434 ++++ .../GetMessageActionEndpointTest.kt | 489 +++++ .../message_actions/ReceiveMessageActions.kt | 230 +++ .../RemoveMessageActionEndpointTest.kt | 333 ++++ .../presence/GetStateEndpointTest.kt | 443 +++++ .../endpoints/presence/HereNowEndpointTest.kt | 68 +- .../legacy/endpoints/presence/LeaveTest.kt | 295 +++ .../presence/SetStateEndpointTest.kt | 449 +++++ .../presence/WhereNowEndpointTest.kt | 376 ++++ .../legacy/endpoints/pubsub/PublishTest.kt | 553 ++++++ .../endpoints/pubsub/SignalTest.kt | 3 +- .../endpoints/pubsub/SubscribeEndpointTest.kt | 5 +- .../endpoints/push/AddChannelsToPushTest.kt | 3 +- .../endpoints/push/ListPushProvisionsTest.kt | 6 +- .../push/PushPayloadHelperHelperTest.kt | 4 +- .../RemoveAllPushChannelsForDeviceTest.kt | 3 +- .../push/RemoveChannelsFromPushTest.kt | 3 +- .../managers/BasePathManagerTest.kt | 5 +- .../managers/PublishSequenceManagerTest.kt | 5 +- .../managers/SubscriptionManagerTest.kt | 32 +- 36 files changed, 6044 insertions(+), 280 deletions(-) delete mode 100644 src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt delete mode 100644 src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt rename src/test/kotlin/com/pubnub/api/{ => legacy}/PubNubTest.kt (88%) create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt rename src/test/kotlin/com/pubnub/api/{ => legacy/endpoints}/EndpointTest.kt (97%) create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/HeartbeatEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt (90%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt (62%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt (69%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/history/FetchMessagesEndpointTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/history/HistoryEndpointTest.kt (99%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/history/MessageCountTest.kt (70%) create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/GetStateEndpointTest.kt rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/presence/HereNowEndpointTest.kt (90%) create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/pubsub/SignalTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/pubsub/SubscribeEndpointTest.kt (99%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/push/AddChannelsToPushTest.kt (99%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/push/ListPushProvisionsTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/push/PushPayloadHelperHelperTest.kt (99%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt (98%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/endpoints/push/RemoveChannelsFromPushTest.kt (99%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/managers/BasePathManagerTest.kt (97%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/managers/PublishSequenceManagerTest.kt (79%) rename src/test/kotlin/com/pubnub/api/{ => legacy}/managers/SubscriptionManagerTest.kt (99%) diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt deleted file mode 100644 index 9f7e0bd88..000000000 --- a/src/test/kotlin/com/pubnub/api/endpoints/presence/GetStateEndpointTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.pubnub.api.endpoints.presence - -import com.github.tomakehurst.wiremock.client.WireMock.aResponse -import com.github.tomakehurst.wiremock.client.WireMock.get -import com.github.tomakehurst.wiremock.client.WireMock.stubFor -import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo -import com.pubnub.api.BaseTest -import com.pubnub.api.PubNubError -import com.pubnub.api.PubNubException -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class GetStateEndpointTest : BaseTest() { - - @Test - fun testOneChannelSync() { - stubFor( - get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) - .willReturn( - aResponse().withBody( - """ - { - "status": 200, - "message": "OK", - "payload": { - "age": 20, - "status": "online" - }, - "service": "Presence" - } - """.trimIndent() - ) - ) - ) - - val result = pubnub.getPresenceState().apply { - channels = listOf("testChannel") - uuid = "sampleUUID" - }.sync()!! - - val ch1Data = result.stateByUUID["testChannel"] - assertEquals(pubnub.mapper.elementToInt(ch1Data!!, "age"), 20) - assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") - } - - @Test - fun testOneChannelWithoutUUIDSync() { - stubFor( - get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID")) - .willReturn( - aResponse().withBody( - """ - { - "status": 200, - "message": "OK", - "payload": { - "age": 20, - "status": "online" - }, - "service": "Presence" - } - """.trimIndent() - ) - ) - ) - - val result = pubnub.getPresenceState().apply { - channels = listOf("testChannel") - }.sync()!! - - val ch1Data = result.stateByUUID["testChannel"] - assertEquals(pubnub.mapper.elementToInt(ch1Data!!, "age"), 20) - assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") - } - - @Test - fun testFailedPayloadSync() { - stubFor( - get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) - .willReturn( - aResponse().withBody( - "{ \"status\": 200, \"message\": \"OK\", \"payload\": \"age\" : 20, " + - "\"status\" : \"online\"}, \"service\": \"Presence\"}" - ) - ) - ) - - try { - pubnub.getPresenceState().apply { - channels = listOf("testChannel") - uuid = "sampleUUID" - }.sync() - } catch (e: Exception) { - e as PubNubException - assertEquals(PubNubError.PARSING_ERROR, e.pubnubError) - println("e.errorMessage ${e.errorMessage}" ) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt b/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt deleted file mode 100644 index 7c44e75f3..000000000 --- a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.pubnub.api.endpoints.pubsub - -import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class PublishTest : BaseTest() { - - @Test - fun testFireSuccessSync() { - stubFor( - get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) - .willReturn(aResponse().withBody("""[1,"Sent","14598111595318003"]""")) - ) - - pubnub.fire().apply { - channel = "coolChannel" - message = "hi" - }.sync()!! - - val requests = findAll(getRequestedFor(urlMatching("/.*"))) - assertEquals(1, requests.size) - assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) - assertEquals("true", requests[0].queryParameter("norep").firstValue()) - assertEquals("0", requests[0].queryParameter("store").firstValue()) - } - - @Test - fun testNoRepSuccessSync() { - stubFor( - get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) - .willReturn(aResponse().withBody("""[1,"Sent","14598111595318003"]""")) - ) - - pubnub.publish().apply { - channel = "coolChannel" - message = "hi" - replicate = false - }.sync()!! - - val requests = findAll(getRequestedFor(urlMatching("/.*"))) - assertEquals(1, requests.size) - assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) - assertEquals("true", requests[0].queryParameter("norep").firstValue()) - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt new file mode 100644 index 000000000..50d9b8794 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt @@ -0,0 +1,44 @@ +package com.pubnub.api.legacy + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNLogVerbosity +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach + +abstract class BaseTest { + + protected val wireMockServer = WireMockServer(wireMockConfig()) + + protected var pubnub = PubNub(PNConfiguration().apply { + subscribeKey = "mySubscribeKey" + publishKey = "myPublishKey" + uuid = "myUUID" + origin = "localhost:8080" + secure = false + logVerbosity = PNLogVerbosity.BODY + }) + + @BeforeEach + fun beforeEach() { + wireMockServer.start() + onBefore() + } + + @AfterEach + fun afterEach() { + wireMockServer.stop() + // assertTrue(wireMockServer.findAllUnmatchedRequests().isEmpty()) + onAfter() + } + + open fun onBefore() { + + } + + open fun onAfter() { + + } +} diff --git a/src/test/kotlin/com/pubnub/api/PubNubTest.kt b/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt similarity index 88% rename from src/test/kotlin/com/pubnub/api/PubNubTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt index ac410367c..1d9b7468a 100644 --- a/src/test/kotlin/com/pubnub/api/PubNubTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt @@ -1,7 +1,11 @@ -package com.pubnub.api +package com.pubnub.api.legacy +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNReconnectionPolicy -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class PubNubTest : BaseTest() { @@ -74,7 +78,7 @@ class PubNubTest : BaseTest() { pubnub = PubNub(config) val version = pubnub.version val timeStamp = pubnub.timestamp() - assertEquals("0.0.1-canary", version) + assertEquals("0.0.2-dev", version) assertTrue(timeStamp > 0) } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt new file mode 100644 index 000000000..9a66f6249 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt @@ -0,0 +1,139 @@ +package com.pubnub.api.legacy.endpoints + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.assertPnException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest +import org.awaitility.Awaitility +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class DeleteMessagesEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "" + } + """.trimIndent() + ) + ) + ) + + pubnub.deleteMessages().apply { + channels = listOf("mychannel,my_channel") + }.sync()!! + } + + @Test + fun testSyncAuthSuccess() { + stubFor( + delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.deleteMessages().apply { + channels = listOf("mychannel,my_channel") + }.sync()!! + + val requests = findAll(deleteRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testFailure() { + stubFor( + delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 403, + "error": false, + "error_message": "wut" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.deleteMessages().apply { + channels = listOf("mychannel,my_channel") + }.sync()!! + failTest() + } catch (e: Exception) { + e.printStackTrace() + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testAsyncSuccess() { + stubFor( + delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "error": false, + "error_message": "" + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.deleteMessages().apply { + channels = listOf("mychannel,my_channel") + }.async { result, status -> + println(status) + result!! + assertFalse(status.error) + assertEquals(PNOperationType.PNDeleteMessagesOperation, status.operation) + success.set(true) + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(success) + } + + @Test + fun testMissingChannel() { + try { + pubnub.deleteMessages().sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/EndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt similarity index 97% rename from src/test/kotlin/com/pubnub/api/EndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt index a395a4a9e..2eea138f8 100644 --- a/src/test/kotlin/com/pubnub/api/EndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt @@ -1,6 +1,8 @@ -package com.pubnub.api +package com.pubnub.api.legacy.endpoints +import com.pubnub.api.Endpoint import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest import okhttp3.Request import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/HeartbeatEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/HeartbeatEndpointTest.kt new file mode 100644 index 000000000..f03b85ce7 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/HeartbeatEndpointTest.kt @@ -0,0 +1,249 @@ +package com.pubnub.api.legacy.endpoints + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.endpoints.presence.Heartbeat +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class HeartbeatEndpointTest : BaseTest() { + + @Test + fun testSuccessOneChannel() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + Heartbeat(pubnub, listOf("ch1")).sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + assertEquals("123", request.queryParameter("heartbeat").firstValue()) + } + + @Test + fun testSuccessManyChannels() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + Heartbeat(pubnub, listOf("ch1", "ch2")).sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + assertEquals("123", request.queryParameter("heartbeat").firstValue()) + } + + @Test + fun testSuccessOneChannelGroup() { + pubnub.configuration.presenceTimeout = 123 + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + Heartbeat(pubnub, channelGroups = listOf("cg1")).sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + assertEquals("cg1", request.queryParameter("channel-group").firstValue()) + assertEquals("123", request.queryParameter("heartbeat").firstValue()) + } + + @Test + fun testSuccessManyChannelGroups() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + Heartbeat(pubnub, channelGroups = listOf("cg1", "cg2")).sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + val request = requests[0] + assertEquals("myUUID", request.queryParameter("uuid").firstValue()) + assertEquals("cg1,cg2", request.queryParameter("channel-group").firstValue()) + assertEquals("123", request.queryParameter("heartbeat").firstValue()) + } + + @Test + fun testMissingChannelAndGroupSync() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + Heartbeat(pubnub).sync()!! + failTest() + } catch (e: Exception) { + assertPnException( + PubNubError.CHANNEL_AND_GROUP_MISSING, + e + ) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + Heartbeat(pubnub, listOf("ch1")).sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testBlankSubKeySync() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.subscribeKey = " " + + try { + Heartbeat(pubnub, listOf("ch1")).sync()!! + failTest() + } catch (e: Exception) { + assertPnException( + PubNubError.SUBSCRIBE_KEY_MISSING, + e + ) + } + } + + @Test + fun testEmptySubKeySync() { + pubnub.configuration.presenceTimeout = 123 + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.subscribeKey = "" + + try { + Heartbeat(pubnub, listOf("ch1")).sync()!! + failTest() + } catch (e: Exception) { + assertPnException( + PubNubError.SUBSCRIBE_KEY_MISSING, + e + ) + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt new file mode 100644 index 000000000..3f427e4f7 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt @@ -0,0 +1,1739 @@ +package com.pubnub.api.legacy.endpoints.access + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData +import org.awaitility.Awaitility +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class GrantEndpointTest : BaseTest() { + + override fun onBefore() { + pubnub.configuration.secretKey = "secretKey" + pubnub.configuration.includeInstanceIdentifier = true + pubnub.configuration.includeRequestIdentifier = true + } + + @Test + fun noGroupsOneChannelOneKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) // todo replace with error + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelTwoKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channels = listOf("ch1") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsTwoChannelOneKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1", "ch2") + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(1, result.channels["ch2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsTwoChannelTwoKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = (listOf("key1", "key2")) + channels = (listOf("ch1", "ch2")) + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(2, result.channels["ch2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key2"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupNoChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel-groups": "cg1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(0, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupNoChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel-groups": "cg1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(0, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupOneChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + }, + "channel-groups": "cg1" + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupOneChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + }, + "channel-groups": "cg1" + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channels = listOf("ch1") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupTwoChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + }, + "channel-groups": "cg1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun oneGroupTwoChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + }, + "channel-groups": "cg1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1") + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(1, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key2"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + + @Test + fun twoGroupNoChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(0, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(1, result.channelGroups["cg2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun twoGroupNoChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(0, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key2"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun twoGroupOneChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + }, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(1, result.channelGroups["cg2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun twoGroupOneChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + }, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channels = listOf("ch1") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(2, result.channelGroups["cg2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key2"]!!.javaClass) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun twoGroupTwoChannelOneKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + }, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(1, result.channelGroups["cg1"]!!.size) + assertEquals(1, result.channelGroups["cg2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(1, result.channels["ch2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun twoGroupTwoChannelTwoKey() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "channel-group+auth", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channels": { + "ch1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "ch2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + }, + "channel-groups": { + "cg1": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "cg2": { + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + }, + "key2": { + "r": 0, + "w": 0, + "m": 0 + } + } + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1", "key2") + channels = listOf("ch1", "ch2") + channelGroups = listOf("cg1", "cg2") + }.sync()!! + + assertEquals(2, result.channels.size) + assertEquals(2, result.channelGroups.size) + assertEquals(2, result.channelGroups["cg1"]!!.size) + assertEquals(2, result.channelGroups["cg2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channelGroups["cg2"]!!["key2"]!!.javaClass) + assertEquals(2, result.channels["ch1"]!!.size) + assertEquals(2, result.channels["ch2"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key2"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key1"]!!.javaClass) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch2"]!!["key2"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelOneKeyTTLTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .withQueryParam("ttl", matching("1334")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + ttl = 1334 + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelOneReadKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("1")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + read = true + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelOneWriteKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("1")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + write = true + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelOneDeleteKeyTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .withQueryParam("d", matching("1")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + delete = true + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun noGroupsOneChannelOneKeyManageTest() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("1")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + manage = true + }.sync()!! + + assertEquals(1, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals(1, result.channels["ch1"]!!.size) + assertEquals(PNAccessManagerKeyData::class.java, result.channels["ch1"]!!["key1"]!!.javaClass) + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + pubnub.configuration.authKey = "myKey" + + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))) + assertEquals(1, requests.size) + + decomposeAndVerifySignature(pubnub.configuration, requests[0]) + } + + @Test + fun testOperationTypeSuccessAsync() { + val atomic = AtomicBoolean(false) + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.async { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAccessManagerGrant, status.operation) + atomic.set(true) + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic) + } + + @Test + fun testBlankSecretKey() { + pubnub.configuration.secretKey = " " + try { + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SECRET_KEY_MISSING, e) + } + } + + @Test + fun testEmptySecretKey() { + pubnub.configuration.secretKey = "" + try { + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SECRET_KEY_MISSING, e) + } + } + + @Test + fun testBlankSubscribeKey() { + pubnub.configuration.subscribeKey = " " + try { + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptySubscribeKey() { + pubnub.configuration.subscribeKey = "" + try { + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testMissingChannelsAndChannelGroup() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withStatus(200).withBody( + """ + { + "message": "Success", + "payload": { + "level": "subkey", + "subscribe_key": "mySubscribeKey", + "ttl": 1440, + "r": 0, + "w": 1, + "m": 0, + "d": 0 + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + try { + val grantResult = pubnub.grant().sync()!! + assertEquals("subkey", grantResult.level) + assertEquals(1440, grantResult.ttl) + assertEquals(0, grantResult.channels.size) + assertEquals(0, grantResult.channelGroups.size) + } catch (e: PubNubException) { + e.printStackTrace() + failTest() + } + } + + @Test + fun testNullPayload() { + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """{"message":"Success","service":"Access Manager","status":200}""" + ) + ) + ) + + try { + pubnub.grant().apply { + authKeys = listOf("key1") + channels = listOf("ch1") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNullAuthKeyAsync() { + val atomic = AtomicBoolean(false) + stubFor( + get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn( + aResponse().withBody( + """ + { + "message": "Success", + "payload": { + "level": "user", + "subscribe_key": "sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f", + "ttl": 1, + "channel": "ch1", + "auths": { + "key1": { + "r": 0, + "w": 0, + "m": 0 + } + } + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + ) + ) + ) + + pubnub.grant().apply { + channels = listOf("ch1") + }.async { result, status -> + if (status.operation == PNOperationType.PNAccessManagerGrant && !status.error) { + atomic.set(true) + } + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic) + } + + // todo does telemetry param count? + +} diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt index d6753d7ab..3fa628497 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.kt @@ -1,9 +1,10 @@ -package com.pubnub.api.endpoints.channel_groups +package com.pubnub.api.legacy.endpoints.channel_groups import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.core.IsEqual import org.junit.jupiter.api.Assertions.* diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt index 737edcf5b..a6f1b8c0e 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt @@ -1,9 +1,10 @@ -package com.pubnub.api.endpoints.channel_groups +package com.pubnub.api.legacy.endpoints.channel_groups import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt similarity index 90% rename from src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt index 4f454bb92..a52a275b2 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt @@ -1,9 +1,10 @@ -package com.pubnub.api.endpoints.channel_groups +package com.pubnub.api.legacy.endpoints.channel_groups import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.core.IsEqual import org.junit.jupiter.api.Assertions @@ -21,14 +22,7 @@ class DeleteChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) .willReturn( aResponse().withBody( - """ - { - "status": 200, - "message": "OK", - "payload": {}, - "service": "ChannelGroups" - } - """.trimIndent() + """{"status":200,"message":"OK","payload":{},"service":"ChannelGroups"}""" ) ) ) @@ -69,8 +63,7 @@ class DeleteChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """{"status": 200, "message": "OK", "payload": {}, "service": "ChannelGroups"}""" ) ) ) diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt similarity index 62% rename from src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt index 975d0a604..93d5264bf 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt @@ -1,9 +1,13 @@ -package com.pubnub.api.endpoints.channel_groups +package com.pubnub.api.legacy.endpoints.channel_groups import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.* +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.assertPnException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers @@ -22,8 +26,19 @@ class ListAllChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + - "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": { + "groups": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -37,10 +52,13 @@ class ListAllChannelGroupEndpointTest : BaseTest() { stubFor( get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) .willReturn( - aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"service\": " + - "\"ChannelGroups\"}" - ) + aResponse().withBody(""" + { + "status": 200, + "message": "OK", + "service": "ChannelGroups" + } + """.trimIndent()) ) ) @@ -73,8 +91,19 @@ class ListAllChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + - "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": { + "groups": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -94,8 +123,19 @@ class ListAllChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + - "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": { + "groups": [ + "a", + "b" + ] + }, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) diff --git a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt similarity index 69% rename from src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt index 6d0cd812e..40f2f67c7 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.kt @@ -1,11 +1,11 @@ -package com.pubnub.api.endpoints.channel_groups +package com.pubnub.api.legacy.endpoints.channel_groups import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest import com.pubnub.api.PubNubError import com.pubnub.api.assertPnException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.core.IsEqual import org.junit.jupiter.api.Assertions @@ -22,8 +22,14 @@ class RemoveChannelChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -40,8 +46,14 @@ class RemoveChannelChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -61,8 +73,14 @@ class RemoveChannelChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -82,8 +100,14 @@ class RemoveChannelChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) @@ -106,8 +130,14 @@ class RemoveChannelChannelGroupEndpointTest : BaseTest() { get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + - "\"service\": \"ChannelGroups\"}" + """ + { + "status": 200, + "message": "OK", + "payload": {}, + "service": "ChannelGroups" + } + """.trimIndent() ) ) ) diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/FetchMessagesEndpointTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/history/FetchMessagesEndpointTest.kt index 00430f79e..138950ff5 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/history/FetchMessagesEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/FetchMessagesEndpointTest.kt @@ -1,7 +1,7 @@ -package com.pubnub.api.endpoints.history +package com.pubnub.api.legacy.endpoints.history import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt index 6b991db9f..4934645e5 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/history/HistoryEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt @@ -1,8 +1,9 @@ -package com.pubnub.api.endpoints.history +package com.pubnub.api.legacy.endpoints.history import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt similarity index 70% rename from src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt index 1de7b019d..70741fa17 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/history/MessageCountTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt @@ -1,9 +1,9 @@ -package com.pubnub.api.endpoints.history +package com.pubnub.api.legacy.endpoints.history import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -44,8 +44,16 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19 + } + } + """.trimIndent() ) ) ) @@ -70,8 +78,16 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19 + } + } + """.trimIndent() ) ) ) @@ -99,8 +115,17 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19, + "new_channel": 5 + } + } + """.trimIndent() ) ) ) @@ -130,8 +155,17 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19, + "new_channel": 5 + } + } + """.trimIndent() ) ) ) @@ -160,8 +194,16 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19 + } + } + """.trimIndent() ) ) ) @@ -187,8 +229,17 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19, + "new_channel": 5 + } + } + """.trimIndent() ) ) ) @@ -214,8 +265,17 @@ class MessageCountTest : BaseTest() { get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19, \"new_channel\":5}}" + """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19, + "new_channel": 5 + } + } + """.trimIndent() ) ) ) @@ -240,10 +300,16 @@ class MessageCountTest : BaseTest() { stubFor( get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) .willReturn( - aResponse().withBody( - "{\"status\": 200, \"error\": false, \"error_message\": \"\", " + - "\"channels\": {\"my_channel\":19}}" - ) + aResponse().withBody(""" + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "my_channel": 19 + } + } + """.trimIndent()) ) ) var exception: PubNubException? = null diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt new file mode 100644 index 000000000..f968728de --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt @@ -0,0 +1,434 @@ +package com.pubnub.api.legacy.endpoints.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class AddMessageActionEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + + assertEquals(result.messageTimetoken, 123) + assertEquals(result.type, "emoji") + assertEquals(result.uuid, "someUuid") // todo what's the uuid? author? + assertEquals(result.value, "smiley") + assertEquals(result.actionTimetoken, 1000) + } + + @Test + fun testAsyncSuccess() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.async { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAddMessageAction, status.operation) + assertEquals(result!!.messageTimetoken, 123) + assertEquals(result.type, "emoji") + assertEquals(result.uuid, "someUuid") + assertEquals(result.value, "smiley") + assertEquals(result.actionTimetoken, 1000) + success.set(true) + } + + success.listen() + } + + @Test + fun testMalformedJson() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + + @Test + fun testEmptyBody() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn(emptyJson()) + ) + + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoBody() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn(noContent()) + ) + + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoData() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200 + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNullData() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": null + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoChannel() { + try { + pubnub.addMessageAction().apply { + + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testBlankChannel() { + try { + pubnub.addMessageAction().apply { + channel = " " + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testNoMessageAction() { + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_MISSING, e) + } + } + + + @Test + fun testBlankType() { + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = " ", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_TYPE_MISSING, e) + } + } + + @Test + fun testBlankValue() { + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = " ", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_VALUE_MISSING, e) + } + } + + @Test + fun testInvalidSubKey() { + pubnub.configuration.subscribeKey = " " + try { + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = " ", + messageTimetoken = 123 + ) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testAuthKeyRequired() { + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.sync()!! + + val requests = findAll(postRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + post(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123")) + .withRequestBody(equalToJson("""{"type":"emoji","value":"smiley"}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.addMessageAction().apply { + channel = "coolChannel" + messageAction = PNMessageAction( + type = "emoji", + value = "smiley", + messageTimetoken = 123 + ) + }.async { _, status -> + println(status) + assertFalse(status.error) + assertEquals(PNOperationType.PNAddMessageAction, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_msga", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt new file mode 100644 index 000000000..3b050741a --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt @@ -0,0 +1,489 @@ +package com.pubnub.api.legacy.endpoints.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class GetMessageActionEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + + assertEquals(result.actions.size, 1) + assertEquals(result.actions[0].messageTimetoken, 123) + assertEquals(result.actions[0].type, "emoji") + assertEquals(result.actions[0].uuid, "someUuid") + assertEquals(result.actions[0].value, "smiley") + assertEquals(result.actions[0].actionTimetoken, 1000) + } + + @Test + fun testSyncSuccessMultipleActions() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody(""" + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "uuid_1", + "value": "โค๏ธ", + "actionTimetoken": "1000" + }, + { + "messageTimetoken": "456", + "type": "reaction", + "uuid": "uuid_2", + "value": "๐Ÿ‘", + "actionTimetoken": "2000" + }, + { + "messageTimetoken": "789", + "type": "emoji", + "uuid": "uuid_2", + "value": "๐Ÿ˜„", + "actionTimetoken": "3000" + } + ] + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + + assertEquals(result.actions.size, 3) + + assertEquals(result.actions[0].messageTimetoken, 123) + assertEquals(result.actions[0].type, "emoji") + assertEquals(result.actions[0].uuid, "uuid_1") + assertEquals(result.actions[0].value, "โค๏ธ") + assertEquals(result.actions[0].actionTimetoken, 1000) + + assertEquals(result.actions[1].messageTimetoken, 456) + assertEquals(result.actions[1].type, "reaction") + assertEquals(result.actions[1].uuid, "uuid_2") + assertEquals(result.actions[1].value, "๐Ÿ‘") + assertEquals(result.actions[1].actionTimetoken, 2000) + + assertEquals(result.actions[2].messageTimetoken, 789) + assertEquals(result.actions[2].type, "emoji") + assertEquals(result.actions[2].uuid, "uuid_2") + assertEquals(result.actions[2].value, "๐Ÿ˜„") + assertEquals(result.actions[2].actionTimetoken, 3000) + } + + + @Test + fun testAsyncSuccess() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.async { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNGetMessageActions, status.operation) + assertEquals(result!!.actions.size, 1) + assertEquals(result.actions[0].messageTimetoken, 123) + assertEquals(result.actions[0].type, "emoji") + assertEquals(result.actions[0].uuid, "someUuid") + assertEquals(result.actions[0].value, "smiley") + assertEquals(result.actions[0].actionTimetoken, 1000) + success.set(true) + } + + success.listen() + } + + @Test + fun testMalformedJson() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley" + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testEmptyBody() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn(emptyJson()) + ) + + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoBody() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn(noContent()) + ) + + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoData() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200 + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testEmptyData() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200 + "data": [] + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + + @Test + fun testNoChannel() { + try { + pubnub.getMessageActions().apply { + + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testBlankChannel() { + try { + pubnub.getMessageActions().apply { + channel = " " + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testInvalidSubKey() { + pubnub.configuration.subscribeKey = " " + try { + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testAuthKeyRequired() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOptionalParams() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .withQueryParam("limit", matching("10")) + .withQueryParam("start", matching("15")) + .withQueryParam("end", matching("20")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.getMessageActions().apply { + channel = "coolChannel" + limit = 10 + start = 15 + end = 20 + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + assertEquals("10", requests[0].queryParameter("limit").firstValue()) + assertEquals("15", requests[0].queryParameter("start").firstValue()) + assertEquals("20", requests[0].queryParameter("end").firstValue()) + } + + @Test + fun testNoOptionalParams() { + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .withQueryParam("limit", absent()) + .withQueryParam("start", absent()) + .withQueryParam("end", absent()) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + assertFalse(requests[0].queryParameter("limit").isPresent) + assertFalse(requests[0].queryParameter("start").isPresent) + assertFalse(requests[0].queryParameter("end").isPresent) + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "123", + "type": "emoji", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + ] + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.getMessageActions().apply { + channel = "coolChannel" + }.async { _, status -> + println(status) + assertFalse(status.error) + assertEquals(PNOperationType.PNGetMessageActions, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_msga", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt new file mode 100644 index 000000000..0990911b3 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt @@ -0,0 +1,230 @@ +package com.pubnub.api.legacy.endpoints.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.listen +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult +import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult +import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class ReceiveMessageActions : BaseTest() { + + @Test + fun testReceiveMessageAction() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "1000", + "r": 12 + }, + "m": [ + { + "a": "1", + "f": 0, + "e": 3, + "i": "client-1639ed91", + "p": { + "t": "1000", + "r": 12 + }, + "k": "mySubscribeKey", + "c": "coolChannel", + "d": { + "source": "actions", + "version": "1.0", + "data": { + "messageTimetoken": "500", + "type": "reaction", + "value": "smiley", + "actionTimetoken": "600" + }, + "event": "added" + } + } + ] + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + failTest() + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + failTest() + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { + failTest() + } + + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) { + failTest() + } + + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) { + failTest() + } + + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) { + failTest() + } + + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) { + assertEquals(pnMessageActionResult.channel, "coolChannel") + assertEquals(pnMessageActionResult.messageAction.messageTimetoken, 500) + assertEquals(pnMessageActionResult.messageAction.uuid, "client-1639ed91") + assertEquals(pnMessageActionResult.messageAction.actionTimetoken, 600) + assertEquals(pnMessageActionResult.messageAction.type, "reaction") + assertEquals(pnMessageActionResult.messageAction.value, "smiley") + success.set(true) + } + }) + + pubnub.subscribe().apply { + channels = listOf("coolChannel") + }.execute() + + success.listen() + + } + + @Test + fun testReceiveMessageActionMulti() { + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn( + aResponse().withBody( + """ + { + "t": { + "t": "1000", + "r": 12 + }, + "m": [ + { + "a": "1", + "f": 0, + "e": 3, + "i": "client-1639ed91", + "p": { + "t": "1000", + "r": 12 + }, + "k": "mySubscribeKey", + "c": "coolChannel", + "d": { + "source": "actions", + "version": "1.0", + "data": { + "messageTimetoken": "500", + "type": "reaction", + "value": "smiley", + "actionTimetoken": "600" + }, + "event": "added" + } + }, + { + "a": "1", + "f": 0, + "e": 3, + "i": "client-1639ed91", + "p": { + "t": "1000", + "r": 12 + }, + "k": "mySubscribeKey", + "c": "coolChannel", + "d": { + "source": "actions", + "version": "1.0", + "data": { + "messageTimetoken": "500", + "type": "reaction", + "value": "grinning", + "actionTimetoken": "699" + }, + "event": "added" + } + } + ] + } + """.trimIndent() + ) + ) + ) + + val count = AtomicInteger() + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + failTest() + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + failTest() + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { + failTest() + } + + override fun user(pubnub: PubNub, pnUserResult: PNUserResult) { + failTest() + } + + override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) { + failTest() + } + + override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) { + failTest() + } + + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) { + count.incrementAndGet() + if (count.get() == 2) { + success.set(true) + } + } + }) + + pubnub.subscribe().apply { + channels = listOf("coolChannel") + }.execute() + + success.listen() + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt new file mode 100644 index 000000000..cad4200db --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt @@ -0,0 +1,333 @@ +package com.pubnub.api.legacy.endpoints.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class RemoveMessageActionEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": {} + } + """.trimIndent() + ) + ) + ) + + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + } + + @Test + fun testAsyncSuccess() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": {} + } + """.trimIndent() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.async { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAddMessageAction, status.operation) + success.set(true) + } + + success.listen() + } + + @Test + fun testMalformedJson() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200 + "data": {} + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + + @Test + fun testEmptyBody() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn(emptyJson()) + ) + + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoBody() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn(noContent()) + ) + + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoData() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200 + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNullData() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": null + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testNoChannel() { + try { + pubnub.removeMessageAction().apply { + + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testBlankChannel() { + try { + pubnub.removeMessageAction().apply { + channel = " " + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testNoMessageTimetoken() { + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_TIMETOKEN_MISSING, e) + } + } + + @Test + fun testNoActionTimetoken() { + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_TIMETOKEN_MISSING, e) + } + } + + @Test + fun testInvalidSubKey() { + pubnub.configuration.subscribeKey = " " + try { + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testAuthKeyRequired() { + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": {} + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "authKey" + + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.sync()!! + + val requests = findAll(deleteRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testTelemetryParam() { + val success = AtomicBoolean() + + stubFor( + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/coolChannel/message/123/action/100")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "data": {} + } + """.trimIndent() + ) + ) + ) + + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + + lateinit var telemetryParamName: String + + pubnub.removeMessageAction().apply { + channel = "coolChannel" + messageTimetoken = 123L + actionTimetoken = 100L + }.async { _, status -> + println(status) + assertFalse(status.error) + assertEquals(PNOperationType.PNAddMessageAction, status.operation) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals("l_msga", telemetryParamName) + success.set(true) + } + + success.listen() + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/GetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/GetStateEndpointTest.kt new file mode 100644 index 000000000..feb34f249 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/GetStateEndpointTest.kt @@ -0,0 +1,443 @@ +package com.pubnub.api.legacy.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class GetStateEndpointTest : BaseTest() { + + @Test + fun testOneChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["testChannel"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + } + + @Test + fun testOneChannelWithoutUUIDSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channels = listOf("testChannel") + }.sync()!! + + val ch1Data = result.stateByUUID["testChannel"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + } + + + @Test + fun testFailedPayloadSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { "status": 200, "message": "OK", "payload": "age" : 20 + """.trimIndent() + ) + ) + ) + + try { + pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testMultipleChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "ch1": { + "age": 20, + "status": "online" + }, + "ch2": { + "age": 100, + "status": "offline" + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channels = listOf("ch1", "ch2") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["ch1"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + + val ch2Data = result.stateByUUID["ch2"]!! + assertEquals(pubnub.mapper.elementToInt(ch2Data, "age"), 100) + assertEquals(pubnub.mapper.elementToString(ch2Data, "status"), "offline") + } + + @Test + fun testOneChannelGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "chcg1": { + "age": 20, + "status": "online" + }, + "chcg2": { + "age": 100, + "status": "offline" + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channelGroups = listOf("cg1") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["chcg1"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + + val ch2Data = result.stateByUUID["chcg2"]!! + assertEquals(pubnub.mapper.elementToInt(ch2Data, "age"), 100) + assertEquals(pubnub.mapper.elementToString(ch2Data, "status"), "offline") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun testManyChannelGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "chcg1": { + "age": 20, + "status": "online" + }, + "chcg2": { + "age": 100, + "status": "offline" + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channelGroups = listOf("cg1", "cg2") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["chcg1"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + + val ch2Data = result.stateByUUID["chcg2"]!! + assertEquals(pubnub.mapper.elementToInt(ch2Data, "age"), 100) + assertEquals(pubnub.mapper.elementToString(ch2Data, "status"), "offline") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1,cg2", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun testCombinationSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "chcg1": { + "age": 20, + "status": "online" + }, + "chcg2": { + "age": 100, + "status": "offline" + } + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.getPresenceState().apply { + channels = listOf("ch1") + channelGroups = listOf("cg1", "cg2") + uuid = "sampleUUID" + }.sync()!! + + val ch1Data = result.stateByUUID["chcg1"]!! + assertEquals(pubnub.mapper.elementToInt(ch1Data, "age"), 20) + assertEquals(pubnub.mapper.elementToString(ch1Data, "status"), "online") + + val ch2Data = result.stateByUUID["chcg2"]!! + assertEquals(pubnub.mapper.elementToInt(ch2Data, "age"), 100) + assertEquals(pubnub.mapper.elementToString(ch2Data, "status"), "offline") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1,cg2", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun testMissingChannelAndGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.getPresenceState().apply { + uuid = "sampleUUID" + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_AND_GROUP_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.async { result, status -> + if (status.operation == PNOperationType.PNGetState) { + atomic.incrementAndGet() + } + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testBlankSubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = " " + + try { + pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptySubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = "" + + try { + pubnub.getPresenceState().apply { + channels = listOf("testChannel") + uuid = "sampleUUID" + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt similarity index 90% rename from src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt index c6e9c56fb..6092cb292 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/presence/HereNowEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt @@ -1,7 +1,7 @@ -package com.pubnub.api.endpoints.presence +package com.pubnub.api.legacy.endpoints.presence import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.PubNubError.SUBSCRIBE_KEY_MISSING import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType.PNHereNowOperation @@ -458,58 +458,38 @@ class HereNowEndpointTest : BaseTest() { @Test fun testOperationTypeSuccessAsync() { stubFor( - get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + get(urlPathMatching("/v2/presence/sub_key/mySubscribeKey.*")) .willReturn( aResponse().withBody( """ - { - "status": 200, - "message": "OK", - "payload": { - "total_occupancy": 3, - "total_channels": 2, - "channels": { - "ch1": { - "occupancy": 1, - "uuids": [ - { - "uuid": "user1", - "state": { - "age": 10 - } - } - ] - }, - "ch2": { - "occupancy": 2, - "uuids": [ - { - "uuid": "user1", - "state": { - "age": 10 - } - }, - { - "uuid": "user3", - "state": { - "age": 30 - } - } - ] - } - } - }, - "service": "Presence" - } - """.trimIndent() + { + "status": 200, + "message": "OK", + "payload": { + "channels": { + "ch1": { + "occupancy": 1, + "uuids": [ + "myUUID" + ] + } + }, + "total_channels": 1, + "total_occupancy": 1 + }, + "service": "Presence" + } + """.trimIndent() ) ) ) val atomic = AtomicInteger(0) - pubnub.hereNow().async { _, status -> + pubnub.hereNow().async { result, status -> if (status.operation == PNHereNowOperation) { + assertEquals(1, result!!.channels.size) + assertEquals(pubnub.configuration.uuid, result.channels["ch1"]!!.occupants[0].uuid) atomic.incrementAndGet(); } } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt new file mode 100644 index 000000000..d1bd1b1b3 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt @@ -0,0 +1,295 @@ +package com.pubnub.api.legacy.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.endpoints.presence.Leave +import com.pubnub.api.legacy.BaseTest +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class LeaveTest : BaseTest() { + + @Test + fun subscribeChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channels = listOf("coolChannel") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun subscribeChannelsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + + @Test + fun subscribeChannelsWithGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + channelGroups = listOf("cg1") + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun subscribeChannelsWithGroupASync() { + val statusArrived = AtomicBoolean() + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channels = listOf("coolChannel", "coolChannel2") + channelGroups = listOf("cg1") + }.async { result, status -> + assertEquals(status.affectedChannels[0], "coolChannel") + assertEquals(status.affectedChannels[1], "coolChannel2") + assertEquals(status.affectedChannelGroups[0], "cg1") + statusArrived.set(true) + } + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic( + statusArrived, + IsEqual.equalTo(true) + ) + } + + @Test + fun subscribeGroupsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channelGroups = listOf("cg1", "cg2") + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1,cg2", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun subscribeGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + Leave(pubnub).apply { + channelGroups = listOf("cg1") + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun testMissingChannelAndGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + try { + Leave(pubnub).apply {}.sync() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_AND_GROUP_MISSING, e) + } + } + + @Test + fun testBlankSubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = " " + + try { + Leave(pubnub).apply {}.sync() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptySubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = "" + + try { + Leave(pubnub).apply {}.sync() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence", + "action": "leave" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + Leave(pubnub).apply { + channels = listOf("coolChannel") + }.sync() + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt new file mode 100644 index 000000000..eeeb7fe75 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt @@ -0,0 +1,449 @@ +package com.pubnub.api.legacy.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class SetStateEndpointTest : BaseTest() { + + @Test + fun applyStateForChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun applyStateForSomebodyElseChannelSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/someoneElseUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + uuid = "someoneElseUUID" + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun applyStateForChannelsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel,testChannel2/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channels = listOf("testChannel", "testChannel2") + state = mapOf("age" to 20) + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + + @Test + fun applyStateForChannelGroupSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channelGroups = listOf("cg1") + state = mapOf("age" to 20) + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun applyStateForChannelGroupsSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channelGroups = listOf("cg1", "cg2") + state = mapOf("age" to 20) + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("cg1,cg2", requests[0].queryParameter("channel-group").firstValue()) + } + + @Test + fun applyStateForMixSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val result = pubnub.setPresenceState().apply { + channels = listOf("ch1") + channelGroups = listOf("cg1", "cg2") + state = mapOf("age" to 20) + }.sync()!! + + assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun applyNon200Sync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ).withStatus(400) + ) + ) + + try { + pubnub.setPresenceState().apply { + channels = listOf("ch1") + channelGroups = listOf("cg1", "cg2") + state = mapOf("age" to 20) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.HTTP_ERROR, e) + } + } + + @Test + fun missingStateSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.setPresenceState().apply { + channels = listOf("testChannel") + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.STATE_MISSING, e) + } + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testBlankSubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + "{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}" + ) + ) + ) + + pubnub.configuration.subscribeKey = " " + + try { + pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptySubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + "{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}" + ) + ) + ) + + pubnub.configuration.subscribeKey = "" + + try { + pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testChannelAndGroupMissingSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "age": 20, + "status": "online" + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.setPresenceState().apply { + state = mapOf("age" to 20) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_AND_GROUP_MISSING, e) + } + } + + @Test + fun testNullPayloadSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("state", equalToJson("""{"age":20}""")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.setPresenceState().apply { + channels = listOf("testChannel") + state = mapOf("age" to 20) + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt new file mode 100644 index 000000000..bb64e4615 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt @@ -0,0 +1,376 @@ +package com.pubnub.api.legacy.endpoints.presence + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.failTest +import org.awaitility.Awaitility +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.atomic.AtomicInteger + +class WhereNowEndpointTest : BaseTest() { + + @Test + fun testSyncSuccess() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.whereNow().sync()!! + assertThat(response.channels, Matchers.contains("a", "b")) // todo compare timestamp param like this + } + + @Test + fun testSyncSuccessCustomUUID() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/customUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val response = pubnub.whereNow().apply { + uuid = "customUUID" + }.sync()!! + + assertThat(response.channels, Matchers.contains("a", "b")) + } + + @Test + fun testSyncBrokenWithString() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\"" + + ": [zimp]}, \"service\": \"Presence\"}" + ) + ) + ) + + try { + pubnub.whereNow().sync() + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testSyncBrokenWithoutJSON() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "zimp}, \"service\": \"Presence\"}" + ) + ) + ) + + try { + pubnub.whereNow().sync() + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + + @Test + fun testSyncBrokenWithout200() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse() + .withStatus(404) + .withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": [\"a\",\"b\"]}," + + " \"service\": \"Presence\"}" + ) + ) + ) + + try { + pubnub.whereNow().sync() + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.HTTP_ERROR, e) + } + } + + @Test + fun testAsyncSuccess() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.whereNow().async { result, status -> + assertFalse(status.error) + assertThat(result!!.channels, Matchers.contains("a", "b")) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testAsyncBrokenWithString() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[zimp]}, \"service\": \"Presence\"}" + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.whereNow().async { result, status -> + assertTrue(status.error) + assertPnException(PubNubError.PARSING_ERROR, status) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testAsyncBrokenWithoutJSON() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + "{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "zimp}, \"service\": \"Presence\"}" + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.whereNow().async { result, status -> + assertTrue(status.error) + assertPnException(PubNubError.PARSING_ERROR, status) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testAsyncBrokenWithout200() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse() + .withStatus(400) + .withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + val atomic = AtomicInteger(0) + + pubnub.whereNow().async { result, status -> + assertTrue(status.error) + assertPnException(PubNubError.HTTP_ERROR, status) + assertEquals(PNStatusCategory.PNBadRequestCategory, status.category) + atomic.incrementAndGet() + } + + Awaitility.await().atMost(5, SECONDS).untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testIsAuthRequiredSuccessSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.authKey = "myKey" + + pubnub.whereNow().sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myKey", requests[0].queryParameter("auth").firstValue()) + } + + @Test + fun testBlankSubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = " " + + try { + pubnub.whereNow().sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptySubKeySync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "a", + "b" + ] + }, + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + pubnub.configuration.subscribeKey = "" + + try { + pubnub.whereNow().sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testNullPayloadSync() { + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn( + aResponse().withBody( + """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + ) + ) + ) + + try { + pubnub.whereNow().sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt new file mode 100644 index 000000000..2aacfc4ae --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt @@ -0,0 +1,553 @@ +package com.pubnub.api.legacy.endpoints.pubsub + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.Gson +import com.pubnub.api.PubNubError +import com.pubnub.api.PubNubException +import com.pubnub.api.assertPnException +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest +import org.awaitility.Awaitility +import org.hamcrest.core.IsEqual +import org.json.JSONArray +import org.json.JSONObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import java.nio.charset.Charset +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class PublishTest : BaseTest() { + + @Test + fun testFireSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.fire().apply { + channel = "coolChannel" + message = "hi" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("true", requests[0].queryParameter("norep").firstValue()) + assertEquals("0", requests[0].queryParameter("store").firstValue()) + } + + @Test + fun testNoRepSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + replicate = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("true", requests[0].queryParameter("norep").firstValue()) + } + + @Test + fun testRepDefaultSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hirep" + replicate = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("true", requests[0].queryParameter("norep").firstValue()) + } + + @Test + fun testSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + replicate = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessSequenceSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + }.sync()!! + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(2, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("1", requests[0].queryParameter("seqn").firstValue()) + assertEquals("2", requests[1].queryParameter("seqn").firstValue()) + } + + @Test + fun testSuccessPostSync() { + stubFor( + post(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = listOf("m1", "m2") + usePost = true + }.sync()!! + + val requests = findAll(postRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals("""["m1","m2"]""", String(requests[0].body, Charset.forName("UTF-8"))) + } + + @Test + fun testSuccessStoreFalseSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + shouldStore = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("0", requests[0].queryParameter("store").firstValue()) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessStoreTrueSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + shouldStore = true + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("1", requests[0].queryParameter("store").firstValue()) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessMetaSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam( + "pnsdk", matching("PubNub-Kotlin/.*") + ) //.withQueryParam("meta", matching("%5B%22m1%22%2C%22m2%22%5D")) + .withQueryParam("meta", equalToJson("""["m1","m2"]""")) + .withQueryParam("store", matching("0")) + .withQueryParam("seqn", matching("1")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + meta = listOf("m1", "m2") + shouldStore = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + } + + @Test + fun testSuccessAuthKeySync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + pubnub.configuration.authKey = "authKey" + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("authKey", requests[0].queryParameter("auth").firstValue()) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessIntSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/10")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = 10 + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessArraySync() { + stubFor( + get( + urlPathEqualTo( + "/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/[%22a%22,%22b%22,%22c%22]" + ) + ).willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + val l = listOf("a", "b", "c") + + pubnub.publish().apply { + channel = "coolChannel" + message = l + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessArrayEncryptedSync() { + stubFor( + get( + urlPathEqualTo( + "/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22HFP7V6bDwBLrwc1t8Rnrog==%22" + ) + ) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + pubnub.configuration.cipherKey = "testCipher" + + pubnub.publish().apply { + channel = "coolChannel" + message = listOf("m1", "m2") + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testSuccessPostEncryptedSync() { + stubFor( + post(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + pubnub.configuration.cipherKey = "testCipher" + + pubnub.publish().apply { + channel = "coolChannel" + message = listOf("m1", "m2") + usePost = true + }.sync()!! + + val requests = findAll(postRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + assertEquals(""""HFP7V6bDwBLrwc1t8Rnrog=="""", String(requests[0].body, Charset.forName("UTF-8"))) + } + + @Test + fun testSuccessHashMapSync() { + val params: MutableMap = HashMap() + params["a"] = 10 + params["z"] = "test" + stubFor( + get( + urlPathEqualTo( + "/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22a%22:10,%22z%22:%22test%22%7D" + ) + ) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = params + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + private class TestPojo() { + + constructor(s1: String, s2: String) : this() { + field1 = s1 + field2 = s2 + } + + private var field1: String? = null + private var field2: String? = null + + } + + @Test + fun testSuccessPOJOSync() { + val testPojo = TestPojo("10", "20") + stubFor( + get( + urlPathEqualTo( + "/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22field1%22:%2210%22,%22field2%22:%2220%22%7D" + ) + ) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + println("pozho ${Gson().toJson(testPojo)}") + + pubnub.publish().apply { + channel = "coolChannel" + message = testPojo + }.sync()!! + + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testJSONObject() { + val testMessage = JSONObject() + testMessage.put("hi", "test") + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22hi%22:%22test%22%7D")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = testMessage.toMap() + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testJSONList() { + val testMessage = JSONArray() + testMessage.put("hi") + testMessage.put("hi2") + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/[%22hi%22,%22hi2%22]")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = testMessage.toList() + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("myUUID", requests[0].queryParameter("uuid").firstValue()) + } + + @Test + fun testMissingChannel() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + try { + pubnub.publish().apply { + message = "hi" + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testEmptyChannel() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","158832720000000000"]""")) + ) + + try { + pubnub.publish().apply { + channel = " " + message = "hi" + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testMissingMessage() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + try { + pubnub.publish().apply { + channel = "coolChannel" + }.sync()!! + failTest() + } catch (e: PubNubException) { + assertPnException(PubNubError.MESSAGE_MISSING, e) + } + } + + @Test + fun testOperationTypeSuccessAsync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + val atomic = AtomicInteger(0) + + pubnub.publish().apply { + + }.async { result, status -> + if (status.operation === PNOperationType.PNPublishOperation) { + atomic.incrementAndGet() + } + } + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, IsEqual.equalTo(1)) + } + + @Test + fun testEmptySubKeySync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.configuration.subscribeKey = "" + + try { + pubnub.publish().apply { + channel = "coolChannel" + message = "hirep" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + + @Test + fun testEmptyPublishKeySync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + pubnub.configuration.publishKey = "" + + try { + pubnub.publish().apply { + channel = "coolChannel" + message = "hirep" + }.sync()!! + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PUBLISH_KEY_MISSING, e) + } + } + + @Test + fun testTTLShouldStoryDefaultSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + ttl = 10 + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("10", requests[0].queryParameter("ttl").firstValue()) + } + + @Test + fun testTTLShouldStoreFalseSuccessSync() { + stubFor( + get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) + ) + + pubnub.publish().apply { + channel = "coolChannel" + message = "hi" + shouldStore = false + }.sync()!! + + val requests = findAll(getRequestedFor(urlMatching("/.*"))) + assertEquals(1, requests.size) + assertEquals("0", requests[0].queryParameter("store").firstValue()) + assertFalse(requests[0].queryParameter("ttl").isPresent) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt index bab3fa741..150d93efa 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SignalTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.endpoints.pubsub +package com.pubnub.api.legacy.endpoints.pubsub import com.github.tomakehurst.wiremock.client.WireMock.* import com.google.gson.Gson import com.pubnub.api.* import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult diff --git a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SubscribeEndpointTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SubscribeEndpointTest.kt index 4fd412204..fb0433376 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SubscribeEndpointTest.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.endpoints.pubsub +package com.pubnub.api.legacy.endpoints.pubsub import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.BaseTest import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.assertPnException +import com.pubnub.api.endpoints.pubsub.Subscribe +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.server.SubscribeMessage import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt index 2261f2684..24a5af84b 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPushTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.endpoints.push +package com.pubnub.api.legacy.endpoints.push import com.github.tomakehurst.wiremock.client.WireMock import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.util.concurrent.atomic.AtomicBoolean diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt index ab1fa4ff4..4a356768a 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/push/ListPushProvisionsTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.endpoints.push +package com.pubnub.api.legacy.endpoints.push import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* @@ -6,8 +6,10 @@ import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNPushEnvironment import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test import java.util.* import java.util.concurrent.atomic.AtomicBoolean diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt index 0a120500b..cc81e6f3c 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt @@ -1,7 +1,7 @@ -package com.pubnub.api.endpoints.push +package com.pubnub.api.legacy.endpoints.push import com.google.gson.Gson -import com.pubnub.api.BaseTest +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.enums.PNPushEnvironment import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.* diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt similarity index 98% rename from src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt index f1a3c5b9f..4eb250eb1 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveAllPushChannelsForDeviceTest.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.endpoints.push +package com.pubnub.api.legacy.endpoints.push import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.util.concurrent.atomic.AtomicBoolean diff --git a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveChannelsFromPushTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveChannelsFromPushTest.kt index e4cd70f3b..95e66d7e2 100644 --- a/src/test/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPushTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/RemoveChannelsFromPushTest.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.endpoints.push +package com.pubnub.api.legacy.endpoints.push import com.github.tomakehurst.wiremock.client.WireMock import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.util.concurrent.atomic.AtomicBoolean diff --git a/src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt b/src/test/kotlin/com/pubnub/api/legacy/managers/BasePathManagerTest.kt similarity index 97% rename from src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/managers/BasePathManagerTest.kt index 550a36909..af8d898d2 100644 --- a/src/test/kotlin/com/pubnub/api/managers/BasePathManagerTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/managers/BasePathManagerTest.kt @@ -1,7 +1,8 @@ -package com.pubnub.api.managers +package com.pubnub.api.legacy.managers -import com.pubnub.api.BaseTest import com.pubnub.api.PNConfiguration +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.managers.BasePathManager import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt b/src/test/kotlin/com/pubnub/api/legacy/managers/PublishSequenceManagerTest.kt similarity index 79% rename from src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/managers/PublishSequenceManagerTest.kt index 556f114cd..f35b1fcae 100644 --- a/src/test/kotlin/com/pubnub/api/managers/PublishSequenceManagerTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/managers/PublishSequenceManagerTest.kt @@ -1,6 +1,7 @@ -package com.pubnub.api.managers +package com.pubnub.api.legacy.managers -import com.pubnub.api.BaseTest +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.managers.PublishSequenceManager import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt similarity index 99% rename from src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt rename to src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt index a7f8ce9dc..b0f164535 100644 --- a/src/test/kotlin/com/pubnub/api/managers/SubscriptionManagerTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt @@ -1,12 +1,15 @@ -package com.pubnub.api.managers +package com.pubnub.api.legacy.managers import com.github.tomakehurst.wiremock.client.WireMock.* import com.google.gson.reflect.TypeToken -import com.pubnub.api.* +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubException +import com.pubnub.api.PubNubUtil import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNHeartbeatNotificationOptions import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult @@ -15,6 +18,7 @@ import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResu import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import com.pubnub.api.toCsv import org.awaitility.Awaitility import org.hamcrest.Matchers import org.hamcrest.core.IsEqual @@ -3127,7 +3131,8 @@ class SubscriptionManagerTest : BaseTest() { get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) .willReturn( aResponse() - .withBody(""" + .withBody( + """ { "t": { "t": null, @@ -3135,7 +3140,8 @@ class SubscriptionManagerTest : BaseTest() { }, "m": [] } - """.trimIndent()) + """.trimIndent() + ) .withStatus(200) ) ) @@ -3144,13 +3150,15 @@ class SubscriptionManagerTest : BaseTest() { .willReturn( aResponse() .withStatus(200) - .withBody(""" + .withBody( + """ { "status": 200, "message": "OK", "service": "Presence" } - """.trimIndent()) + """.trimIndent() + ) ) ) pubnub.addListener(object : SubscribeCallback() { @@ -3204,7 +3212,8 @@ class SubscriptionManagerTest : BaseTest() { get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) .willReturn( aResponse() - .withBody(""" + .withBody( + """ { "t": { "t": null, @@ -3212,7 +3221,8 @@ class SubscriptionManagerTest : BaseTest() { }, "m": [] } - """.trimIndent()) + """.trimIndent() + ) .withStatus(200) ) ) @@ -3222,13 +3232,15 @@ class SubscriptionManagerTest : BaseTest() { .willReturn( aResponse() .withStatus(200) - .withBody(""" + .withBody( + """ { "status": 200, "message": "OK", "service": "Presence" } - """.trimIndent()) + """.trimIndent() + ) ) ) From b6464db2e880c32408c4600fea5db3bda2282824 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:39:27 +0200 Subject: [PATCH 56/98] Update integration tests --- src/test/kotlin/com/pubnub/api/BaseTest.kt | 109 ----------- src/test/kotlin/com/pubnub/api/Utils.kt | 175 ++++++++++++++++++ .../pubnub/api/{ => integration}/AppTest.kt | 13 +- .../api/integration/BaseIntegrationTest.kt | 134 ++++++++++++++ .../integration/PlayGroundIntegrationTest.kt | 161 ++++++++++++++++ 5 files changed, 479 insertions(+), 113 deletions(-) delete mode 100644 src/test/kotlin/com/pubnub/api/BaseTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/Utils.kt rename src/test/kotlin/com/pubnub/api/{ => integration}/AppTest.kt (95%) create mode 100644 src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt diff --git a/src/test/kotlin/com/pubnub/api/BaseTest.kt b/src/test/kotlin/com/pubnub/api/BaseTest.kt deleted file mode 100644 index a2fec0b8d..000000000 --- a/src/test/kotlin/com/pubnub/api/BaseTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.pubnub.api - -import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.WireMock -import com.pubnub.api.enums.PNLogVerbosity -import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.models.consumer.PNStatus -import org.awaitility.Awaitility -import org.awaitility.Durations -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.time.Duration -import java.util.concurrent.atomic.AtomicBoolean - -abstract class BaseTest { - - private val wireMockServer: WireMockServer = WireMockServer() - - protected var pubnub = PubNub(PNConfiguration().apply { - subscribeKey = "mySubscribeKey" - publishKey = "myPublishKey" - uuid = "myUUID" - origin = "localhost:8080" - secure = false - logVerbosity = PNLogVerbosity.BODY - }) - - @BeforeEach - fun beforeEach() { - onBefore() - wireMockServer.start() - } - - @AfterEach - fun afterEach() { - wireMockServer.stop() - onAfter() - } - - open fun onBefore() { - - } - - open fun onAfter() { - - } -} - -interface IEndpointTest { - fun provideTelemetryParameterName(): String - fun providePnOperation(): PNOperationType - fun provideKeyMatrix(): Int - @Test - fun testTelemetryParameter() - fun provideWorkingCode(): Endpoint<*, *> -} - -val SUB = 0x001 -val PUB = 0x010 -val AUTH = 0x100 - -private fun observe(success: AtomicBoolean) { - Awaitility.await() - .atMost(Durations.FIVE_SECONDS) - .with() - .until(success::get) -} - -fun AtomicBoolean.listen(): AtomicBoolean { - this.set(false) - observe(this) - return this -} - -fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { - Awaitility.await() - .atMost(Durations.FIVE_SECONDS) - .with() - .until { - function.invoke() - } - return this -} - -fun PNStatus.printQueryParams() { - this.clientRequest!!.url().queryParameterNames().map { - print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") - } -} - -fun assertPnException(expectedPubNubError: PubNubError, pnStatus: PNStatus) { - Assertions.assertTrue(pnStatus.error) - Assertions.assertEquals(expectedPubNubError, pnStatus.exception!!.pubnubError) -} - -fun assertPnException(expectedPubNubError: PubNubError, exception: Exception) { - exception as PubNubException - Assertions.assertEquals(expectedPubNubError, exception.pubnubError) -} - -fun PNStatus.param(param: String) = clientRequest!!.url().queryParameter(param) - -fun emptyJson() = WireMock.aResponse().withBody("{}") - -fun failTest(message: String? = null) { - Assertions.fail(message) -} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/Utils.kt b/src/test/kotlin/com/pubnub/api/Utils.kt new file mode 100644 index 000000000..628e250ce --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/Utils.kt @@ -0,0 +1,175 @@ +package com.pubnub.api + +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.verification.LoggedRequest +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.suite.SpecialChar +import okhttp3.HttpUrl +import okhttp3.Request +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.atomic.AtomicBoolean +import java.util.stream.Collectors + + +private fun observe(success: AtomicBoolean) { + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .until(success::get) +} + +fun AtomicBoolean.listen(): AtomicBoolean { + this.set(false) + observe(this) + return this +} + +fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .until { + function.invoke() + } + return this +} + +fun PNStatus.printQueryParams() { + this.clientRequest!!.url().queryParameterNames().map { + print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") + } +} + +fun assertPnException(expectedPubNubError: PubNubError, pnStatus: PNStatus) { + Assertions.assertTrue(pnStatus.error) + assertEquals(expectedPubNubError, pnStatus.exception!!.pubnubError) +} + +fun assertPnException(expectedPubNubError: PubNubError, exception: Exception) { + exception as PubNubException + assertEquals(expectedPubNubError, exception.pubnubError) +} + +fun PNStatus.param(param: String) = clientRequest!!.url().queryParameter(param) + +fun PNStatus.encodedParam(param: String) = clientRequest!!.url().encodedQuery()!!.encodedParamString(param) + +fun String.encodedParamString(param: String): String { + return split("&") + .first { it.startsWith(param) } + .split("=")[1] +} + +fun emptyJson() = WireMock.aResponse().withBody("{}") + +fun failTest(message: String? = null) { + Assertions.fail(message) +} + +private fun decomposeAndVerifySignature( + configuration: PNConfiguration, + url: String, + method: String, + body: String = "" +) { + val httpUrl = HttpUrl.parse(url) + println(httpUrl) + + val sortedQueryString = httpUrl!!.run { + queryParameterNames() + .filter { it != "signature" } + .map { it to PubNubUtil.pamEncode(queryParameterValues(it).first()) } + .toMap() + .toSortedMap() + .map { "${it.key}=${it.value}" } + .joinToString("&") + } + + var v2Signature = true + + val input = + if (!(httpUrl.encodedPath().startsWith("/publish") && method.toLowerCase() == "post")) { + """ + ${method.toUpperCase()} + ${configuration.publishKey} + ${httpUrl.encodedPath()} + $sortedQueryString + $body + """.trimIndent() + } else { + v2Signature = false + """ + ${configuration.subscribeKey} + ${configuration.publishKey} + ${httpUrl.encodedPath()} + $sortedQueryString + """.trimIndent() + } + + val actualSignature = httpUrl.queryParameter("signature") + val verifiedSignature = verifyViaPython(configuration.secretKey, input, v2Signature) + + val rebuiltSignature = PubNubUtil.signSHA256(configuration.secretKey, input).run { + if (v2Signature) "v2.${this.trim('=')}" + else this + } + + println("originalTimestamp:\t${httpUrl.queryParameter("timestamp")}") + println("signatureInput:\t$input") + println("actualSignature:\t$actualSignature") + println("rebuiltSignature:\t$rebuiltSignature") + println("verifiedSignature:\t$verifiedSignature") + + assertEquals(actualSignature, rebuiltSignature) + assertEquals(actualSignature, verifiedSignature) +} + +fun decomposeAndVerifySignature(configuration: PNConfiguration, request: LoggedRequest) { + decomposeAndVerifySignature( + configuration = configuration, + url = request.absoluteUrl, + method = request.method.name, + body = request.bodyAsString + ) +} + +fun decomposeAndVerifySignature(configuration: PNConfiguration, request: Request) { + decomposeAndVerifySignature( + configuration = configuration, + url = request.url().toString(), + method = request.method(), + body = PubNubUtil.requestBodyToString(request)!! + ) +} + +private fun verifyViaPython(key: String, input: String, v2Signature: Boolean): String { + val i = input.replace("\n", "###") + val path = ClassLoader.getSystemClassLoader().getResource("hmacsha256.py")!!.path + val command = "python $path $key $i $v2Signature" + val process = Runtime.getRuntime().exec(command) + return BufferedReader(InputStreamReader(process.inputStream)).readLine() +} + +fun getSpecialCharsMap(): List { + val resourceFileAsString = getResourceFileAsString("special_chars.json") + return Gson().fromJson(resourceFileAsString, object : TypeToken>() {}.type) +} + +fun getResourceFileAsString(fileName: String?): String? { + val classLoader = ClassLoader.getSystemClassLoader() + classLoader.getResourceAsStream(fileName).use { inputStream -> + if (inputStream == null) return null + InputStreamReader(inputStream).use { isr -> + BufferedReader(isr).use { reader -> + return reader.lines().collect(Collectors.joining(System.lineSeparator())) + } + } + } +} diff --git a/src/test/kotlin/com/pubnub/api/AppTest.kt b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt similarity index 95% rename from src/test/kotlin/com/pubnub/api/AppTest.kt rename to src/test/kotlin/com/pubnub/api/integration/AppTest.kt index 3f561e5ac..50c2cebed 100644 --- a/src/test/kotlin/com/pubnub/api/AppTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt @@ -1,12 +1,14 @@ -package com.pubnub.api +package com.pubnub.api.integration +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNLogVerbosity import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNOperationType.PNTimeOperation -import com.pubnub.api.enums.PNPushType import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.enums.PNStatusCategory.PNUnknownCategory +import com.pubnub.api.listen import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult @@ -15,6 +17,7 @@ import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResu import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult +import com.pubnub.api.printQueryParams import org.awaitility.Awaitility import org.awaitility.Durations import org.junit.jupiter.api.* @@ -55,6 +58,8 @@ class AppTest { @Test fun testPublishSync() { + val list = (0..9).map { it.toString() } + ('A'..'Z') + println(list) pubnub.publish().apply { channel = UUID.randomUUID().toString() message = UUID.randomUUID().toString() @@ -128,7 +133,7 @@ class AppTest { @Test fun testSynchronizedAccess() { - val size = 60 + val size = 500 var counter = 0 pubnub.addListener(object : SubscribeCallback() { @@ -195,7 +200,7 @@ class AppTest { .containsAll(expectedChannels) } - val pnHereNowResult = pubnub.hereNow().apply { + pubnub.hereNow().apply { channels = expectedChannels includeUUIDs = false includeState = false diff --git a/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt new file mode 100644 index 000000000..4ebb248a6 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt @@ -0,0 +1,134 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.enums.PNLogVerbosity +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInstance +import java.util.* +import java.util.UUID + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class BaseIntegrationTest { + + companion object { + const val TIMEOUT_MEDIUM = 5 + const val TIMEOUT_LOW = 2 + lateinit var PUB_KEY: String + lateinit var SUB_KEY: String + lateinit var PAM_PUB_KEY: String + lateinit var PAM_SUB_KEY: String + lateinit var PAM_SEC_KEY: String + } + + lateinit var pubnub: PubNub + lateinit var server: PubNub + + private var mGuestClients = mutableListOf() + + @BeforeAll + fun extractProperties() { + val inputStream = javaClass.classLoader.getResourceAsStream("config.properties") + val properties = Properties() + properties.load(inputStream) + PUB_KEY = properties.getProperty("pub_key") + SUB_KEY = properties.getProperty("sub_key") + PAM_PUB_KEY = properties.getProperty("pam_pub_key") + PAM_SUB_KEY = properties.getProperty("pam_sub_key") + PAM_SEC_KEY = properties.getProperty("pam_sec_key") + } + + @BeforeEach + open fun before(): Unit { + onPrePubnub() + pubnub = createPubNub() + if (needsServer()) { + server = createServer() + } + onBefore() + } + + @AfterEach + open fun after() { + onAfter() + destroyClient(pubnub) + for (guestClient in mGuestClients) { + destroyClient(guestClient) + } + } + + protected fun createPubNub(): PubNub { + var pnConfiguration = provideStagingConfiguration() + if (pnConfiguration == null) { + pnConfiguration = getBasicPnConfiguration() + } + val pubNub = PubNub(pnConfiguration) + registerGuestClient(pubNub) + return pubNub + } + + private fun createServer(): PubNub { + val pubNub = PubNub(getServerPnConfiguration()) + registerGuestClient(pubNub) + return pubNub + } + + private fun registerGuestClient(guestClient: PubNub) { + mGuestClients.add(guestClient) + } + + protected open fun getBasicPnConfiguration(): PNConfiguration { + val pnConfiguration = PNConfiguration() + if (!needsServer()) { + pnConfiguration.subscribeKey = SUB_KEY + pnConfiguration.publishKey = PUB_KEY + } else { + pnConfiguration.subscribeKey = PAM_SUB_KEY + pnConfiguration.publishKey = PAM_PUB_KEY + pnConfiguration.authKey = provideAuthKey()!! + } + pnConfiguration.logVerbosity = PNLogVerbosity.BODY + pnConfiguration.uuid = "client-${UUID.randomUUID()}" + return pnConfiguration + } + + private fun getServerPnConfiguration(): PNConfiguration { + val pnConfiguration = PNConfiguration() + pnConfiguration.subscribeKey = PAM_SUB_KEY + pnConfiguration.publishKey = PAM_PUB_KEY + pnConfiguration.secretKey = PAM_SEC_KEY + pnConfiguration.logVerbosity = PNLogVerbosity.BODY + pnConfiguration.uuid = "server-${UUID.randomUUID()}" + return pnConfiguration + } + + private fun destroyClient(client: PubNub) { + client.unsubscribeAll() + client.forceDestroy() + } + + private fun needsServer() = provideAuthKey() != null + + protected open fun onBefore() {} + protected open fun onAfter() {} + protected open fun onPrePubnub() {} + + protected open fun provideAuthKey(): String? { + return null + } + + protected open fun provideStagingConfiguration(): PNConfiguration? { + return null + } + +} + +fun randomValue(length: Int = 10): String { + return ((0..9).toList().map(Int::toString) + ('A'..'Z').toList().map(Char::toString)) + .shuffled() + .toList() + .take(length) + .joinToString(separator = "") +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt new file mode 100644 index 000000000..fe1676a55 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt @@ -0,0 +1,161 @@ +package com.pubnub.api.integration + +import com.pubnub.api.decomposeAndVerifySignature +import com.pubnub.api.encodedParam +import com.pubnub.api.getSpecialCharsMap +import com.pubnub.api.listen +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.stream.Stream + + +class PlayGroundIntegrationTest : BaseIntegrationTest() { + + @Test + fun testSpecialCharsPathAndUrl() { + val expectedChannel = randomValue() + val keyName = "special_char" + val value = getSpecialCharsMap().map { it.regular }.shuffled().joinToString("") + + val success = AtomicBoolean() + + server.publish().apply { + channel = expectedChannel + message = value + shouldStore = true + meta = mapOf( + keyName to value + ) + queryParam = mapOf( + "za" to value, + "aa" to value, + "s" to value, + "Zz" to value, + "ZZZ" to value, + "123" to value + ) + }.async { _, status -> + assertFalse(status.error) + decomposeAndVerifySignature(server.configuration, status.clientRequest!!) + success.set(true) + } + + success.listen() + + Awaitility.await() + .pollInterval(Durations.FIVE_HUNDRED_MILLISECONDS) + .atMost(Durations.FIVE_SECONDS) + .until { + val (messages, _, _) = server.history().apply { + channel = expectedChannel + includeMeta = true + }.sync()!! + + assertEquals(1, messages.size) + assertEquals(value, messages[0].meta!!.asJsonObject[keyName].asString) + assertEquals(value, messages[0].entry.asString) + true + } + + } + + @DisplayName("EncodingTestSuite") + @ParameterizedTest(name = "testWith \"{0}\" {1}") + @MethodSource("provideSpecialCharsStream") + fun testVerifySignature(name: String, regular: String, encoded: String) { + println("testVerifySignature: $name $regular $encoded") + + val success = AtomicBoolean() + + server.configuration.includeRequestIdentifier = false + server.configuration.includeInstanceIdentifier = false + + val expectedChannel = randomValue() + val expectedMessage = "msg${regular}msg" + val expectedMetadata = "meta${regular}meta" + + val propertyName = "target" + + server.publish().apply { + channel = expectedChannel + message = expectedMessage + meta = expectedMetadata + queryParam = mapOf(propertyName to regular) + shouldStore = true + usePost = false + }.async { result, status -> + assertFalse(status.error) + // val encodedParam = status.clientRequest!!.url().encodedQuery()!!.encodedParam(propertyName) + val encodedParam = status.encodedParam(propertyName) + println("Checkking $name $encoded $encodedParam") + assertEquals(encoded, encodedParam) + decomposeAndVerifySignature(server.configuration, status.clientRequest!!) + success.set(true) + } + + success.listen() + + val historyTest = { + Awaitility.await() + .pollInterval(Durations.FIVE_HUNDRED_MILLISECONDS) + .atMost(Durations.FIVE_SECONDS) + .until { + val latch = CountDownLatch(1) + server.history().apply { + channel = expectedChannel + includeMeta = true + }.async { result, status -> + assertEquals(1, result!!.messages.size) + decomposeAndVerifySignature(server.configuration, status.clientRequest!!) + assertFalse(status.error) + assertEquals( + expectedMessage, + result.messages[0].entry.asString + ) + assertEquals( + expectedMetadata, + result.messages[0].meta!!.asString + ) + assertEquals( + regular, + result.messages[0].entry.asString.replace("msg", "") + ) + assertEquals( + regular, + result.messages[0].meta!!.asString.replace("meta", "") + ) + latch.countDown() + } + latch.await(1, TimeUnit.SECONDS) + } + } + + historyTest.invoke() + } + + + companion object { + + @JvmStatic + fun provideSpecialCharsStream(): Stream { + return getSpecialCharsMap() + .map { Arguments.of(it.name, it.regular, it.encoded) } + .toList() + .shuffled() + .stream() + } + } + + override fun provideAuthKey() = "" + +} \ No newline at end of file From e62392067436bfbec19f06b32177e8db4fdfc64f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:43:16 +0200 Subject: [PATCH 57/98] Implement Test Suite structure --- .../com/pubnub/api/suite/EndpointTestSuite.kt | 336 ++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt new file mode 100644 index 000000000..679100f6c --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt @@ -0,0 +1,336 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.stubbing.StubMapping +import com.pubnub.api.* +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.models.consumer.PNStatus +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +typealias MoreChecks = (status: PNStatus) -> Unit + +abstract class EndpointTestSuite, R> : BaseTest() { + + private lateinit var expectedStub: StubMapping + + abstract fun telemetryParamName(): String + abstract fun pnOperation(): PNOperationType + abstract fun requiredKeys(): Int + abstract fun snippet(): T + abstract fun verifyResultExpectations(result: R) + abstract fun successfulResponseBody(): String + abstract fun unsuccessfulResponseBodyList(): List + abstract fun mappingBuilder(): MappingBuilder + abstract fun affectedChannelsAndGroups(): Pair, List> + + open fun optionalScenarioList(): List = emptyList() + + override fun onBefore() { + super.onBefore() + expectedStub = stubFor(mappingBuilder().willReturn(aResponse().withBody(successfulResponseBody()))) + } + + @Test + fun testTelemetryParameter() { + val success = AtomicBoolean() + + stubTimeEndpoint() + + lateinit var telemetryParamName: String + + snippet().eval { _, status -> + assertFalse(status.error) + assertEquals(pnOperation(), status.operation) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + telemetryParamName = "l_${status.operation.queryParam}" + assertEquals(telemetryParamName(), telemetryParamName) + } + + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + success.set(true) + } + + success.listen() + } + + @Test + fun testSuccessAsync() { + snippet().eval { result, status -> + assertFalse(status.error) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + assertEquals(pnOperation(), status.operation) + assertEquals(status.affectedChannels, affectedChannelsAndGroups().first) + assertEquals(status.affectedChannelGroups, affectedChannelsAndGroups().second) + verifyResultExpectations(result!!) + } + } + + @Test + fun testSuccessSync() { + runSync() + } + + @Test + fun testKeys() { + testSubscribeKey() + testPublishKey() + testAuthKeySync() + testAuthKeyAsync() + } + + @Test + fun testWrongResponsesSync() { + wireMockServer.removeStub(expectedStub) + + unsuccessfulResponseBodyList().forEach { + val stub = stubFor(mappingBuilder().willReturn(aResponse().withBody(it))) + try { + testSuccessSync() + failTest() + } catch (e: Exception) { + // assertPnException(PubNubError.PARSING_ERROR, e) + } + wireMockServer.removeStub(stub) + } + } + + @Test + fun testWrongResponsesAsync() { + wireMockServer.removeStub(expectedStub) + + unsuccessfulResponseBodyList().forEach { + val stub = stubFor(mappingBuilder().willReturn(aResponse().withBody(it))) + snippet().eval { _, status -> + assertTrue(status.error) + assertPnException(PubNubError.PARSING_ERROR, status) + } + wireMockServer.removeStub(stub) + } + } + + @Test + fun testUsualWrongResponsesSync() { + wireMockServer.removeStub(expectedStub) + + val list = listOf(emptyJson(), noContent(), aResponse().withBody("{}")) + + list.forEach { + val stub = stubFor(mappingBuilder().willReturn(it)) + try { + testSuccessSync() + failTest() + } catch (e: Exception) { + assertPnException(PubNubError.PARSING_ERROR, e) + } + wireMockServer.removeStub(stub) + } + } + + @Test + fun testOptionalResponsesSync() { + wireMockServer.removeStub(expectedStub) + + optionalScenarioList().forEach { + val stub = stubFor(mappingBuilder().willReturn(it.build())) + + try { + snippet().sync()!! + if (it.result == Result.FAIL) { + failTest() + } + } catch (e: Exception) { + if (it.result == Result.FAIL) { + it.pnError?.let { pubNubError -> + assertPnException(pubNubError, e) + } + } + } + + wireMockServer.removeStub(stub) + } + } + + @Test + fun testOptionalResponsesAsync() { + wireMockServer.removeStub(expectedStub) + + optionalScenarioList().forEach { + val stub = stubFor(mappingBuilder().willReturn(it.build())) + + snippet().eval { result, status -> + it.additionalChecks.invoke(status) + if (it.result == Result.SUCCESS) { + assertFalse(status.error) + result!! + } else if (it.result == Result.FAIL) { + assertTrue(status.error) + assertNull(result) + it.pnError?.let { pubNubError -> + assertPnException(pubNubError, status) + } + } + } + + wireMockServer.removeStub(stub) + } + } + + @Test + fun testUrlEncoding() { + snippet().apply { + queryParam = getSpecialCharsMap().map { + it.name to it.regular + }.toMap() + }.eval { result, status -> + assertFalse(status.error) + assertNotNull(result) + + getSpecialCharsMap().shuffled().forEach { + val encodedParam = status.encodedParam(it.name) + assertEquals(it.encoded, encodedParam) + } + } + } + + private fun testSubscribeKey() { + pubnub.configuration.subscribeKey = " " + try { + testSuccessSync() + if (requiredKeys().contains(SUB)) { + failTest() + } + } catch (e: Exception) { + if (requiredKeys().contains(SUB)) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + pubnub.configuration.subscribeKey = "mySubscribeKey" + } + + private fun testPublishKey() { + pubnub.configuration.publishKey = " " + try { + testSuccessSync() + if (requiredKeys().contains(PUB)) { + failTest() + } + } catch (e: Exception) { + if (requiredKeys().contains(PUB)) { + assertPnException(PubNubError.PUBLISH_KEY_MISSING, e) + } + } + pubnub.configuration.publishKey = "myPublishKey" + } + + private fun testAuthKeySync() { + pubnub.configuration.authKey = "someAuthKey" + testSuccessSync() + + val requests = findAll(anyRequestedFor(mappingBuilder().build().request.urlMatcher)) + + if (requiredKeys().contains(AUTH)) { + assertEquals(pubnub.configuration.authKey, requests.last().queryParameter("auth").firstValue()) + } else { + requests.forEach { + assertFalse(it.queryParams.containsKey("auth")) + } + } + } + + private fun testAuthKeyAsync() { + pubnub.configuration.authKey = "someAuthKey" + + snippet().eval { result, status -> + assertFalse(status.error) + + if (requiredKeys().contains(AUTH)) { + assertEquals(pubnub.configuration.authKey, status.param("auth")) + } else { + assertNull(status.param("auth")) + } + } + } + + private fun runSync() { + val result = snippet().sync()!! + verifyResultExpectations(result) + } + + private fun stubTimeEndpoint() { + stubFor( + get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]")) + ) + } +} + +private fun extractKeys(n: Int): List { + val keys = mutableListOf() + var n = n + while (n > 0) { + val power = highestPowerOf2(n); + keys.add(power) + n -= power + } + return keys +} + +private fun highestPowerOf2(n: Int): Int { + var res = 0 + for (i in n downTo 1) { + // If i is a power of 2 + if (i and i - 1 == 0) { + res = i + break + } + } + return res +} + +val SUB = 0b001 +val PUB = 0b010 +val AUTH = 0b100 + +private fun Int.contains(sub: Int): Boolean { + return extractKeys(this).contains(sub) +} + +private fun Endpoint.eval(pieceOfCode: (result: Output?, status: PNStatus) -> Unit) { + val success = AtomicBoolean() + async { result, status -> + pieceOfCode.invoke(result, status) + success.set(true) + } + success.listen() +} + +class OptionalScenario { + var responseBuilder: ResponseDefinitionBuilder.() -> ResponseDefinitionBuilder = { this } + + var additionalChecks: MoreChecks = {} + var result: Result = Result.SUCCESS + var pnError: PubNubError? = null + + internal fun build(): ResponseDefinitionBuilder { + return aResponse().responseBuilder() + } +} + +enum class Result { + SUCCESS, + FAIL +} + +class SpecialChar( + val name: String, + val regular: String, + val encoded: String +) \ No newline at end of file From 1111a4e9b1a553b43cc10829d086a70519d43b2a Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:44:34 +0200 Subject: [PATCH 58/98] Add Python signature verification script --- src/test/resources/hmacsha256.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/test/resources/hmacsha256.py diff --git a/src/test/resources/hmacsha256.py b/src/test/resources/hmacsha256.py new file mode 100644 index 000000000..ab2092325 --- /dev/null +++ b/src/test/resources/hmacsha256.py @@ -0,0 +1,34 @@ +#!/usr/bin/python + +import hashlib +import hmac +import base64 +import sys +import subprocess + +secret = sys.argv[1] +data = sys.argv[2].replace("###", "\n") +is_v2_signature = sys.argv[3] + +message = "{0}".format(data) + +hash = hmac.new(secret, message, hashlib.sha256) + +# to lowercase hexits +hash.hexdigest() + +# to base64 +result = base64.b64encode(hash.digest()) + +result = result.replace('+','-') +result = result.replace('/','_') +result = result.replace('/n','') + +if is_v2_signature == 'true': + while result.endswith('='): + result = result[:-1] + signature = 'v2.'+result +else: + signature = result + +print(signature) \ No newline at end of file From c7804c9d73e3c949b8892df6edf11751765b9593 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:45:35 +0200 Subject: [PATCH 59/98] Implement Test Suite for History APIs --- .../api/suite/history/HistoryMetaTestSuite.kt | 99 ++++++ .../api/suite/history/HistoryTestSuite.kt | 70 ++++ src/test/resources/special_chars.json | 327 ++++++++++++++++++ 3 files changed, 496 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt create mode 100644 src/test/resources/special_chars.json diff --git a/src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt new file mode 100644 index 000000000..7b5623071 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt @@ -0,0 +1,99 @@ +package com.pubnub.api.suite.history + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.History +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNHistoryResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue + +class HistoryMetaTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_hist" + + override fun pnOperation() = PNOperationType.PNHistoryOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): History { + return pubnub.history().apply { + channel = "foo" + includeMeta = true + includeTimetoken = true + } + } + + override fun verifyResultExpectations(result: PNHistoryResult) { + assertEquals(100, result.startTimetoken) + assertEquals(200, result.endTimetoken) + assertEquals(2, result.messages.size) + assertEquals("msg1", result.messages[0].entry.asString) + assertEquals("msg2", result.messages[1].entry.asString) + assertTrue(result.messages[0].meta!!.asString.isBlank()) + assertEquals( + mapOf("color" to "red"), Gson().fromJson( + result.messages[1].meta!!.asJsonObject, + object : TypeToken?>() {}.type + ) + ) + assertEquals(100, result.messages[0].timetoken) + assertEquals(200, result.messages[1].timetoken) + } + + override fun successfulResponseBody() = """ + [ + [ + { + "message": "msg1", + "timetoken": 100, + "meta": "" + }, + { + "message": "msg2", + "timetoken": 200, + "meta": { + "color": "red" + } + } + ], + 100, + 200 + ] + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + "[]", + "[{}]" + ) + + override fun mappingBuilder(): MappingBuilder { + return get( + urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/foo") + ) + .withQueryParam("include_token", equalTo("true")) + .withQueryParam("count", equalTo("100")) + .withQueryParam("include_meta", equalTo("true")) + .withQueryParam("reverse", equalTo("false")) + } + + override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + + override fun optionalScenarioList() = listOf( + OptionalScenario().apply { + responseBuilder = { + withBody("""["First Element Not An Array",0,0]""") + } + additionalChecks = { status -> + assertTrue(status.error) + assertEquals(status.exception!!.errorMessage, "History is disabled") + } + result = Result.FAIL + pnError = PubNubError.HTTP_ERROR + } + ) +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt new file mode 100644 index 000000000..f97f75bdd --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt @@ -0,0 +1,70 @@ +package com.pubnub.api.suite.history + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.History +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNHistoryResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class HistoryTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_hist" + + override fun pnOperation() = PNOperationType.PNHistoryOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): History { + return pubnub.history().apply { + channel = "foo" + } + } + + override fun verifyResultExpectations(result: PNHistoryResult) { + assertEquals(100, result.startTimetoken) + assertEquals(200, result.endTimetoken) + assertEquals(2, result.messages.size) + assertEquals("msg1", result.messages[0].entry.asString) + assertEquals("msg2", result.messages[1].entry.asString) + assertNull(result.messages[0].meta) + assertNull(result.messages[1].meta) + assertNull(result.messages[0].timetoken) + assertNull(result.messages[1].timetoken) + } + + override fun successfulResponseBody() = """[["msg1","msg2"],100,200]""" + + override fun unsuccessfulResponseBodyList() = listOf( + "[]", + "[{}]" + ) + + override fun mappingBuilder(): MappingBuilder { + return get( + urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/foo") + ) + .withQueryParam("include_token", equalTo("false")) + .withQueryParam("count", equalTo("100")) + .withQueryParam("include_meta", equalTo("false")) + .withQueryParam("reverse", equalTo("false")) + } + + override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + + override fun optionalScenarioList() = listOf( + OptionalScenario().apply { + responseBuilder = { + withBody("""["First Element Not An Array",0,0]""") + } + additionalChecks = { status -> + assertTrue(status.error) + assertEquals(status.exception!!.errorMessage, "History is disabled") + } + result = Result.FAIL + pnError = PubNubError.HTTP_ERROR + } + ) +} \ No newline at end of file diff --git a/src/test/resources/special_chars.json b/src/test/resources/special_chars.json new file mode 100644 index 000000000..286418ca2 --- /dev/null +++ b/src/test/resources/special_chars.json @@ -0,0 +1,327 @@ +[ + { + "name": "space", + "regular": " ", + "encoded": "%20" + }, + { + "name": "double_quote", + "regular": "\"", + "encoded": "%22" + }, + { + "name": "single_quote", + "regular": "'", + "encoded": "%27" + }, + { + "name": "exclamation", + "regular": "!", + "encoded": "%21" + }, + { + "name": "right_double_quotation_mark", + "regular": "โ€", + "encoded": "%E2%80%9D" + }, + { + "name": "hash", + "regular": "#", + "encoded": "%23" + }, + { + "name": "dollar_sign", + "regular": "$", + "encoded": "%24" + }, + { + "name": "percent", + "regular": "%", + "encoded": "%25" + }, + { + "name": "ampersand", + "regular": "&", + "encoded": "%26" + }, + { + "name": "right_single_quotation_mark", + "regular": "โ€™", + "encoded": "%E2%80%99" + }, + { + "name": "left_parenthesis", + "regular": "(", + "encoded": "%28" + }, + { + "name": "right_parenthesis", + "regular": ")", + "encoded": "%29" + }, + { + "name": "asterisk", + "regular": "*", + "encoded": "*" + }, + { + "name": "plus", + "regular": "+", + "encoded": "%2B" + }, + { + "name": "comma", + "regular": ",", + "encoded": "%2C" + }, + { + "name": "minus", + "regular": "-", + "encoded": "-" + }, + { + "name": "full_stop", + "regular": ".", + "encoded": "." + }, + { + "name": "slash", + "regular": "/", + "encoded": "%2F" + }, + { + "name": "colon", + "regular": ":", + "encoded": "%3A" + }, + { + "name": "semicolon", + "regular": ";", + "encoded": "%3B" + }, + { + "name": "less_than", + "regular": "<", + "encoded": "%3C" + }, + { + "name": "equal_sign", + "regular": "=", + "encoded": "%3D" + }, + { + "name": "greater_than", + "regular": ">", + "encoded": "%3E" + }, + { + "name": "question_mark", + "regular": "?", + "encoded": "%3F" + }, + { + "name": "at_sign", + "regular": "@", + "encoded": "%40" + }, + { + "name": "left_bracket", + "regular": "[", + "encoded": "%5B" + }, + { + "name": "backslash", + "regular": "\\", + "encoded": "%5C" + }, + { + "name": "right_bracket", + "regular": "]", + "encoded": "%5D" + }, + { + "name": "caret", + "regular": "^", + "encoded": "%5E" + }, + { + "name": "underscore", + "regular": "_", + "encoded": "_" + }, + { + "name": "backtick", + "regular": "`", + "encoded": "%60" + }, + { + "name": "left_brace", + "regular": "{", + "encoded": "%7B" + }, + { + "name": "vertical_bar", + "regular": "|", + "encoded": "%7C" + }, + { + "name": "right_brace", + "regular": "}", + "encoded": "%7D" + }, + { + "name": "tilde", + "regular": "~", + "encoded": "%7E" + }, + { + "name": "inverted_exclamation_mark", + "regular": "ยก", + "encoded": "%C2%A1" + }, + { + "name": "cent_sign", + "regular": "ยข", + "encoded": "%C2%A2" + }, + { + "name": "pound_sign", + "regular": "ยฃ", + "encoded": "%C2%A3" + }, + { + "name": "currency_sign", + "regular": "ยค", + "encoded": "%C2%A4" + }, + { + "name": "yen_sign", + "regular": "ยฅ", + "encoded": "%C2%A5" + }, + { + "name": "broken_bar", + "regular": "ยฆ", + "encoded": "%C2%A6" + }, + { + "name": "section_sign", + "regular": "ยง", + "encoded": "%C2%A7" + }, + { + "name": "diaeresis", + "regular": "ยจ", + "encoded": "%C2%A8" + }, + { + "name": "copyright_sign", + "regular": "ยฉ", + "encoded": "%C2%A9" + }, + { + "name": "feminine_ordinal_indicator", + "regular": "ยช", + "encoded": "%C2%AA" + }, + { + "name": "left-pointing_double_angle_quotation_mark", + "regular": "ยซ", + "encoded": "%C2%AB" + }, + { + "name": "not_sign", + "regular": "ยฌ", + "encoded": "%C2%AC" + }, + { + "name": "registered_sign", + "regular": "ยฎ", + "encoded": "%C2%AE" + }, + { + "name": "macron", + "regular": "ยฏ", + "encoded": "%C2%AF" + }, + { + "name": "degree_sign", + "regular": "ยฐ", + "encoded": "%C2%B0" + }, + { + "name": "plus-minus_sign", + "regular": "ยฑ", + "encoded": "%C2%B1" + }, + { + "name": "superscript_two", + "regular": "ยฒ", + "encoded": "%C2%B2" + }, + { + "name": "superscript_three", + "regular": "ยณ", + "encoded": "%C2%B3" + }, + { + "name": "acute_accent", + "regular": "ยด", + "encoded": "%C2%B4" + }, + { + "name": "micro_sign", + "regular": "ยต", + "encoded": "%C2%B5" + }, + { + "name": "pilcrow_sign", + "regular": "ยถ", + "encoded": "%C2%B6" + }, + { + "name": "middle_dot", + "regular": "ยท", + "encoded": "%C2%B7" + }, + { + "name": "cedilla", + "regular": "ยธ", + "encoded": "%C2%B8" + }, + { + "name": "superscript_one", + "regular": "ยน", + "encoded": "%C2%B9" + }, + { + "name": "masculine_ordinal_indicator", + "regular": "ยบ", + "encoded": "%C2%BA" + }, + { + "name": "right-pointing_double_angle_quotation_mark", + "regular": "ยป", + "encoded": "%C2%BB" + }, + { + "name": "vulgar_fraction_one_quarter", + "regular": "ยผ", + "encoded": "%C2%BC" + }, + { + "name": "vulgar_fraction_one_half", + "regular": "ยฝ", + "encoded": "%C2%BD" + }, + { + "name": "vulgar_fraction_three_quarters", + "regular": "ยพ", + "encoded": "%C2%BE" + }, + { + "name": "inverted_question_mark", + "regular": "ยฟ", + "encoded": "%C2%BF" + } +] \ No newline at end of file From 978b820fe09f88a3331d17a2fa021a305912286e Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:47:01 +0200 Subject: [PATCH 60/98] Implement Test Suite for MessageAction APIs --- .../api/suite/MessageActionTestSuite.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt new file mode 100644 index 000000000..2d3975d4b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt @@ -0,0 +1,63 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.message_actions.AddMessageAction +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import org.junit.jupiter.api.Assertions.assertEquals + +class MessageActionTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_msga" + + override fun pnOperation() = PNOperationType.PNAddMessageAction + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): AddMessageAction { + return pubnub.addMessageAction().apply { + channel = "ch1" + messageAction = PNMessageAction( + type = "reaction", + value = "smiley", + messageTimetoken = 1000 + ) + } + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "data": { + "messageTimetoken": "123", + "type": "reaction", + "uuid": "someUuid", + "value": "smiley", + "actionTimetoken": "1000" + } + } + """.trimIndent() + + + override fun unsuccessfulResponseBodyList() = listOf( + """{"status":200,"data":null}""", + """{"status":200}""" + ) + + override fun verifyResultExpectations(result: PNAddMessageActionResult) { + assertEquals(123, result.messageTimetoken) + assertEquals("reaction", result.type) + assertEquals("someUuid", result.uuid) + assertEquals("smiley", result.value) + assertEquals(1000, result.actionTimetoken) + } + + override fun mappingBuilder() = + post(urlMatching("/v1/message-actions/mySubscribeKey/channel/ch1/message/1000.*")) + .withRequestBody( + equalToJson("""{"type":"reaction","value":"smiley"}""") + ) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() +} \ No newline at end of file From 52db688090dee221f57f4057777f8c72a5dd6b90 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:47:14 +0200 Subject: [PATCH 61/98] Implement Test Suite for Publish APIs --- .../pubnub/api/suite/PublishGetTestSuite.kt | 60 +++++++++++++++++ .../pubnub/api/suite/PublishPostTestSuite.kt | 65 +++++++++++++++++++ .../com/pubnub/api/suite/SignalTestSuite.kt | 49 ++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt new file mode 100644 index 000000000..91a7344ce --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt @@ -0,0 +1,60 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.google.gson.Gson +import com.pubnub.api.endpoints.pubsub.Publish +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNPublishResult +import org.junit.jupiter.api.Assertions.assertEquals +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class PublishGetTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pub" + + override fun pnOperation() = PNOperationType.PNPublishOperation + + override fun requiredKeys() = SUB + PUB + AUTH + + override fun snippet(): Publish { + return pubnub.publish().apply { + channel = "foo" + message = "bar" + } + } + + override fun verifyResultExpectations(result: PNPublishResult) { + assertEquals(15883272000000000L, result.timetoken) + } + + override fun successfulResponseBody() = """[1,"Sent","15883272000000000"]""" + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get( + urlPathEqualTo( + "/publish/myPublishKey/mySubscribeKey/0/foo/0/%s".format( + URLEncoder.encode(Gson().toJson("bar"), StandardCharsets.UTF_8.name()) + ) + ) + )!! + } + + override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + + /*companion object { + @JvmStatic + fun predefined(): Stream? { + return Stream.of( + Arguments.of("foo"), + Arguments.of(123), + Arguments.of(3.14) + ) + } + }*/ + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt new file mode 100644 index 000000000..60dd7f4f4 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt @@ -0,0 +1,65 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.Gson +import com.pubnub.api.endpoints.pubsub.Publish +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNPublishResult +import org.json.JSONObject +import org.junit.jupiter.api.Assertions.assertEquals + +class PublishPostTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pub" + + override fun pnOperation() = PNOperationType.PNPublishOperation + + override fun requiredKeys() = SUB + PUB + AUTH + + override fun snippet(): Publish { + return pubnub.publish().apply { + channel = "foo" + message = mapOf( + "name" to "john", + "age" to 30, + "private" to false + ) + meta = JSONObject().apply { + put("city", "sf") + }.toMap() + usePost = true + } + } + + override fun verifyResultExpectations(result: PNPublishResult) { + assertEquals(15883272000000000L, result.timetoken) + } + + override fun successfulResponseBody() = """[1,"Sent","15883272000000000"]""" + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return post( + urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/foo/0") + ) + .withQueryParam( + "meta", equalToJson("""{"city":"sf"}""") + ) + .withRequestBody( + equalToJson( + Gson().toJson( + mapOf( + "name" to "john", + "age" to 30, + "private" to false + ) + ) + ) + )!! + } + + override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt new file mode 100644 index 000000000..98500122f --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.google.gson.Gson +import com.pubnub.api.endpoints.pubsub.Signal +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNPublishResult +import org.junit.jupiter.api.Assertions.assertEquals +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class SignalTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_sig" + + override fun pnOperation() = PNOperationType.PNSignalOperation + + override fun requiredKeys() = SUB + PUB + AUTH + + override fun snippet(): Signal { + return pubnub.signal().apply { + channel = "foo" + message = "bar" + } + } + + override fun verifyResultExpectations(result: PNPublishResult) { + assertEquals(15883272000000000L, result.timetoken) + } + + override fun successfulResponseBody() = """[1,"Sent","15883272000000000"]""" + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get( + urlPathEqualTo( + "/signal/myPublishKey/mySubscribeKey/0/foo/0/%s".format( + URLEncoder.encode(Gson().toJson("bar"), StandardCharsets.UTF_8.name()) + ) + ) + )!! + } + + override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + +} \ No newline at end of file From ce7cc05af7be143c617beea34c0488f23a1d099f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:47:23 +0200 Subject: [PATCH 62/98] Implement Test Suite for Time API --- .../com/pubnub/api/suite/TimeTestSuite.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt new file mode 100644 index 000000000..1ee07747d --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt @@ -0,0 +1,56 @@ +package com.pubnub.api.suite + +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlMatching +import com.pubnub.api.endpoints.Time +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNTimeResult +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue + +class TimeTestSuite : EndpointTestSuite() { + override fun telemetryParamName() = "l_time" + + override fun pnOperation() = PNOperationType.PNTimeOperation + + override fun requiredKeys() = 0 + + override fun snippet(): Time { + return pubnub.time().apply { + queryParam = mapOf( + "key_1_space" to "ab cd", + "key_2_plus" to "ab+cd" + ) + } + } + + override fun verifyResultExpectations(result: PNTimeResult) { + assertEquals(1000, result.timetoken) + } + + override fun successfulResponseBody() = """[1000]""" + + override fun unsuccessfulResponseBodyList() = listOf( + """{}""", + """[false]""" + ) + + override fun optionalScenarioList(): List { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("[wrong]") } + result = Result.FAIL + additionalChecks = { status -> + assertTrue(status.error) + } + }, + OptionalScenario().apply { + responseBuilder = { withBody("[123]") } + } + ) + } + + override fun mappingBuilder() = get(urlMatching("/time/0.*")) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() +} \ No newline at end of file From 8bf727bab825aefb79f6a7c93c3833f65bc3cdb3 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Wed, 27 May 2020 16:56:56 +0200 Subject: [PATCH 63/98] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index a42089d1d..7462032b1 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ # kotlin + +## Using your PubNub keys + +If you would like to run integration tests against your keys, Execute the following commands to add your publish, subscribe and secret keys to your local copy of the SDK: + + +```bash +cd src/test/resources/ +echo pub_key=YOUR_PUB_KEY >> config.properties +echo sub_key=YOUR_SUB_KEY >> config.properties +echo pam_pub_key=YOUR_PAM_PUB_KEY >> config.properties +echo pam_sub_key=YOUR_PAM_SUB_KEY >> config.properties +echo pam_sec_key=YOUR_PAM_SEC_KEY >> config.properties +``` \ No newline at end of file From f28507c8ee2f54d1294a9f299ca0f934cc5ad741 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:19:42 +0200 Subject: [PATCH 64/98] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c5298fd18..62814d098 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ build/ *.class bin/ gen/ +out/ /src/test/resources/config.properties \ No newline at end of file From 535928c01d0f696a025b2b32660f89b996fb436f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:21:00 +0200 Subject: [PATCH 65/98] Update JUnit5 dependencies --- build.gradle | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a7de90320..c759bc1ab 100644 --- a/build.gradle +++ b/build.gradle @@ -17,9 +17,12 @@ configurations { ktlint } +ext { + junitVersion = '5.6.2' +} + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - testImplementation "org.junit.jupiter:junit-jupiter:5.5.2" implementation("com.squareup.retrofit2:retrofit:2.6.2") @@ -38,10 +41,17 @@ dependencies { implementation 'com.github.tomakehurst:wiremock:2.26.3' implementation group: 'org.json', name: 'json', version: '20190722' + + // testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + // testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + // testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" } test { - useJUnitPlatform() + useJUnitPlatform { + includeEngines 'junit-jupiter' + } } compileKotlin { From 59ad314447a004515d06cecae30807eed986ae4a Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:21:57 +0200 Subject: [PATCH 66/98] Fix response parsing issues in Grant APIs --- .../kotlin/com/pubnub/api/endpoints/access/Grant.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt b/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt index ea224feb6..316feb54e 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/access/Grant.kt @@ -63,8 +63,8 @@ class Grant(pubnub: PubNub) : Endpoint, PNAc override fun createResponse(input: Response>): PNAccessManagerGrantResult? { val data = input.body()!!.payload!! - val constructedChannels = mutableMapOf>() - val constructedGroups = mutableMapOf>() + val constructedChannels = mutableMapOf?>() + val constructedGroups = mutableMapOf?>() // we have a case of a singular channel. data.channel?.let { @@ -82,13 +82,13 @@ class Grant(pubnub: PubNub) : Endpoint, PNAc } data.channels?.forEach { - constructedChannels[it.key] = data.channels[it.key]!!.authKeys!! + constructedChannels[it.key] = data.channels[it.key]!!.authKeys } return PNAccessManagerGrantResult( - level = data.level, + level = data.level!!, ttl = data.ttl, - subscribeKey = data.subscribeKey, + subscribeKey = data.subscribeKey!!, channels = constructedChannels, channelGroups = constructedGroups ) @@ -105,6 +105,7 @@ class Grant(pubnub: PubNub) : Endpoint, PNAc pnAccessManagerKeyData.manageEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "m")) pnAccessManagerKeyData.writeEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "w")) pnAccessManagerKeyData.readEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "r")) + pnAccessManagerKeyData.deleteEnabled = (pubnub.mapper.getAsBoolean(keyMap.value, "d")) result[keyMap.key] = pnAccessManagerKeyData } return result From 3b70237051dd5f1545d156148d326f33f95c4f22 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:22:47 +0200 Subject: [PATCH 67/98] Improve Channel Group APIs --- .../endpoints/channel_groups/AddChannelChannelGroup.kt | 7 +++---- .../channel_groups/AllChannelsChannelGroup.kt | 9 +++------ .../api/endpoints/channel_groups/DeleteChannelGroup.kt | 10 +++------- .../endpoints/channel_groups/ListAllChannelGroup.kt | 6 +++--- .../channel_groups/RemoveChannelChannelGroup.kt | 7 +++---- .../com/pubnub/api/services/ChannelGroupService.kt | 6 +++--- 6 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt index a398085db..d6945b0b7 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.kt @@ -3,12 +3,11 @@ package com.pubnub.api.endpoints.channel_groups import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAddChannelResult -import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response import java.util.* -class AddChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsAddChannelResult>(pubnub) { +class AddChannelChannelGroup(pubnub: PubNub) : Endpoint(pubnub) { lateinit var channelGroup: String lateinit var channels: List @@ -26,7 +25,7 @@ class AddChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannel } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { if (channels.isNotEmpty()) { queryParams["add"] = channels.toCsv() } @@ -39,7 +38,7 @@ class AddChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannel ) } - override fun createResponse(input: Response>): PNChannelGroupsAddChannelResult? { + override fun createResponse(input: Response): PNChannelGroupsAddChannelResult? { return PNChannelGroupsAddChannelResult() } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt index 1f6e1d7ee..e68448f52 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.kt @@ -35,12 +35,9 @@ class AllChannelsChannelGroup(pubnub: PubNub) : } override fun createResponse(input: Response>>): PNChannelGroupsAllChannelsResult? { - // todo test dot of null - return PNChannelGroupsAllChannelsResult().apply { - input.body()!!.payload!!["channels"]?.let { - channels = it as List - } - } + return PNChannelGroupsAllChannelsResult( + input.body()!!.payload!!["channels"] as List + ) } override fun operationType() = PNOperationType.PNChannelsForGroupOperation diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt index 725cf4c57..f5203f33c 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.kt @@ -6,12 +6,11 @@ import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult -import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response import java.util.* -class DeleteChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsDeleteGroupResult>(pubnub) { +class DeleteChannelGroup(pubnub: PubNub) : Endpoint(pubnub) { lateinit var channelGroup: String @@ -24,7 +23,7 @@ class DeleteChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGrou override fun getAffectedChannelGroups() = listOf(channelGroup) - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { return pubnub.retrofitManager.channelGroupService .deleteChannelGroup( pubnub.configuration.subscribeKey, @@ -33,10 +32,7 @@ class DeleteChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGrou ) } - override fun createResponse(input: Response>): PNChannelGroupsDeleteGroupResult? { - if (input.body() == null) { - throw PubNubException(PubNubError.PARSING_ERROR) - } + override fun createResponse(input: Response): PNChannelGroupsDeleteGroupResult? { return PNChannelGroupsDeleteGroupResult() } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt index 35d81405b..f993a7366 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.kt @@ -20,9 +20,9 @@ class ListAllChannelGroup(pubnub: PubNub) : Endpoint>, } override fun createResponse(input: Response>>): PNChannelGroupsListAllResult? { - return PNChannelGroupsListAllResult().apply { - groups = input.body()!!.payload!!["groups"] as List - } + return PNChannelGroupsListAllResult( + input.body()!!.payload!!["groups"] as List + ) } override fun operationType() = PNOperationType.PNChannelGroupsOperation diff --git a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt index 3f6ef65ab..494a488da 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.kt @@ -3,12 +3,11 @@ package com.pubnub.api.endpoints.channel_groups import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult -import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response import java.util.* -class RemoveChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChannelGroupsRemoveChannelResult>(pubnub) { +class RemoveChannelChannelGroup(pubnub: PubNub) : Endpoint(pubnub) { lateinit var channelGroup: String lateinit var channels: List @@ -27,7 +26,7 @@ class RemoveChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChan } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { if (channels.isNotEmpty()) { queryParams["remove"] = channels.toCsv() } @@ -40,7 +39,7 @@ class RemoveChannelChannelGroup(pubnub: PubNub) : Endpoint, PNChan ) } - override fun createResponse(input: Response>): PNChannelGroupsRemoveChannelResult? { + override fun createResponse(input: Response): PNChannelGroupsRemoveChannelResult? { return PNChannelGroupsRemoveChannelResult() } diff --git a/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt b/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt index adf0953ae..637611621 100644 --- a/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt +++ b/src/main/kotlin/com/pubnub/api/services/ChannelGroupService.kt @@ -26,20 +26,20 @@ internal interface ChannelGroupService { @Path("subKey") subKey: String, @Path("group") group: String, @QueryMap options: Map - ): Call> + ): Call @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") fun removeChannel( @Path("subKey") subKey: String, @Path("group") group: String, @QueryMap options: Map - ): Call> + ): Call @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}/remove") fun deleteChannelGroup( @Path("subKey") subKey: String, @Path("group") group: String, @QueryMap options: Map - ): Call> + ): Call } \ No newline at end of file From dbb67b7bcf4c54ba9928dc1952b7eac1ed59af56 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:23:21 +0200 Subject: [PATCH 68/98] Improve Message Action APIs --- .../api/endpoints/message_actions/GetMessageActions.kt | 4 +--- .../endpoints/message_actions/RemoveMessageAction.kt | 10 ++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt index 4ed9dc57b..713f678e6 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/GetMessageActions.kt @@ -44,9 +44,7 @@ class GetMessageActions(pubnub: PubNub) : } override fun createResponse(input: Response>>): PNGetMessageActionsResult? { - return PNGetMessageActionsResult().apply { - actions = input.body()!!.data!! - } + return PNGetMessageActionsResult(input.body()!!.data!!) } override fun operationType() = PNOperationType.PNGetMessageActions diff --git a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt index f460361bf..98fb42ce3 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.kt @@ -6,12 +6,11 @@ import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult -import com.pubnub.api.models.server.objects_api.EntityEnvelope import retrofit2.Call import retrofit2.Response import java.util.* -class RemoveMessageAction(pubnub: PubNub) : Endpoint, PNRemoveMessageActionResult>(pubnub) { +class RemoveMessageAction(pubnub: PubNub) : Endpoint(pubnub) { lateinit var channel: String var messageTimetoken: Long? = null @@ -32,7 +31,7 @@ class RemoveMessageAction(pubnub: PubNub) : Endpoint, PNRemo override fun getAffectedChannels() = listOf(channel) - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { return pubnub.retrofitManager.messageActionService .deleteMessageAction( subKey = pubnub.configuration.subscribeKey, @@ -43,10 +42,9 @@ class RemoveMessageAction(pubnub: PubNub) : Endpoint, PNRemo ) } - override fun createResponse(input: Response>): PNRemoveMessageActionResult? { - input.body()!!.data!! + override fun createResponse(input: Response): PNRemoveMessageActionResult? { return PNRemoveMessageActionResult() } - override fun operationType() = PNOperationType.PNAddMessageAction + override fun operationType() = PNOperationType.PNDeleteMessageAction } \ No newline at end of file From d56baaf356cd5cc8ffdb4643867ba2bb9713bc04 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:24:21 +0200 Subject: [PATCH 69/98] Improve Presence APIs --- .../com/pubnub/api/endpoints/presence/GetState.kt | 8 +++----- .../com/pubnub/api/endpoints/presence/Heartbeat.kt | 11 ++++++----- .../kotlin/com/pubnub/api/endpoints/presence/Leave.kt | 6 +++--- .../com/pubnub/api/endpoints/presence/SetState.kt | 11 ++++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt index efd85ce32..bf00f10e1 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/GetState.kt @@ -42,18 +42,16 @@ class GetState(pubnub: PubNub) : Endpoint, PNGetStateResul override fun createResponse(input: Response>): PNGetStateResult? { val stateMappings = hashMapOf() if (channels.size == 1 && channelGroups.isEmpty()) { - stateMappings[channels.first()] = input.body()?.payload!! + stateMappings[channels.first()] = input.body()!!.payload!! } else { - val it = pubnub.mapper.getObjectIterator(input.body()?.payload!!) + val it = pubnub.mapper.getObjectIterator(input.body()!!.payload!!) while (it.hasNext()) { val stateMapping = it.next() stateMappings[stateMapping.key] = stateMapping.value } } - return PNGetStateResult( - stateByUUID = stateMappings - ) + return PNGetStateResult(stateMappings) } override fun operationType() = PNOperationType.PNGetState diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt index ad21bf4fd..0e365196a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Heartbeat.kt @@ -5,16 +5,15 @@ import com.pubnub.api.PubNub import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType -import com.pubnub.api.models.server.Envelope import retrofit2.Call import retrofit2.Response import java.util.* -internal class Heartbeat( +class Heartbeat internal constructor( pubnub: PubNub, val channels: List = listOf(), val channelGroups: List = listOf() -) : Endpoint, Boolean>(pubnub) { +) : Endpoint(pubnub) { override fun getAffectedChannels() = channels override fun getAffectedChannelGroups() = channelGroups @@ -26,7 +25,7 @@ internal class Heartbeat( } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { queryParams["heartbeat"] = pubnub.configuration.presenceTimeout.toString() if (channelGroups.isNotEmpty()) { @@ -46,7 +45,9 @@ internal class Heartbeat( ) } - override fun createResponse(input: Response>) = true + override fun createResponse(input: Response): Boolean? { + return true + } override fun operationType() = PNOperationType.PNHeartbeatOperation diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt index 9df58834a..57d0e1e05 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/Leave.kt @@ -6,7 +6,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -internal class Leave(pubnub: PubNub) : Endpoint(pubnub) { +class Leave internal constructor(pubnub: PubNub) : Endpoint(pubnub) { var channels = emptyList() var channelGroups = emptyList() @@ -21,7 +21,7 @@ internal class Leave(pubnub: PubNub) : Endpoint(pubnub) { override fun getAffectedChannels() = channels override fun getAffectedChannelGroups() = channelGroups - override fun doWork(queryParams: HashMap): Call { + override fun doWork(queryParams: HashMap): Call { queryParams["channel-group"] = channelGroups.toCsv() return pubnub.retrofitManager.presenceService.leave( @@ -31,7 +31,7 @@ internal class Leave(pubnub: PubNub) : Endpoint(pubnub) { ) } - override fun createResponse(input: Response) = true + override fun createResponse(input: Response) = true override fun operationType() = PNOperationType.PNUnsubscribeOperation } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt index 4ee35e2b5..560923ceb 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/presence/SetState.kt @@ -1,6 +1,7 @@ package com.pubnub.api.endpoints.presence import com.google.gson.JsonElement +import com.google.gson.JsonNull import com.pubnub.api.* import com.pubnub.api.builder.StateOperation import com.pubnub.api.enums.PNOperationType @@ -10,8 +11,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -class SetState(pubnub: PubNub) : - Endpoint, PNSetStateResult>(pubnub) { +class SetState(pubnub: PubNub) : Endpoint, PNSetStateResult>(pubnub) { var channels = emptyList() var channelGroups = emptyList() @@ -57,9 +57,10 @@ class SetState(pubnub: PubNub) : } override fun createResponse(input: Response>): PNSetStateResult? { - return PNSetStateResult( - state = input.body()!!.payload!! - ) + if (input.body()!!.payload!! is JsonNull) { + throw PubNubException(PubNubError.PARSING_ERROR) + } + return PNSetStateResult(input.body()!!.payload!!) } override fun operationType() = PNOperationType.PNSetStateOperation From 8d748ae55ea22aa60d669a85984d97365b6c157a Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:25:05 +0200 Subject: [PATCH 70/98] Handle more exceptions --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 38 +++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index f49298be4..7918f791a 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -108,6 +108,7 @@ abstract class Endpoint(protected val pubnub: PubNub) { errorMessage = errorString jso = errorJson.toString() statusCode = response.code() + affectedCall = call } val pnStatusCategory = when (response.code()) { @@ -352,7 +353,7 @@ abstract class Endpoint(protected val pubnub: PubNub) { private fun checkAndCreateResponse(input: Response): Output? { try { return createResponse(input) - } catch (pubnubException: PubNubException ){ + } catch (pubnubException: PubNubException) { throw pubnubException.apply { statusCode = input.code() jso = pubnub.mapper.toJson(input.body()) @@ -379,6 +380,41 @@ abstract class Endpoint(protected val pubnub: PubNub) { statusCode = input.code() jso = pubnub.mapper.toJson(input.body()) } + } catch (e: NullPointerException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } catch (e: IllegalArgumentException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } catch (e: TypeCastException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } catch (e: ClassCastException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } + } catch (e: UninitializedPropertyAccessException) { + throw PubNubException(PubNubError.PARSING_ERROR).apply { + errorMessage = e.toString() + affectedCall = call + statusCode = input.code() + jso = pubnub.mapper.toJson(input.body()) + } } } From b18f9d717390be78b48626fc1db7fe3b3e4f6f0c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:26:20 +0200 Subject: [PATCH 71/98] Remove GrantToken APIs --- .../access_manager/v3/GrantTokenRequestBody.kt | 4 ---- .../com/pubnub/api/services/AccessManagerService.kt | 12 +++--------- 2 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt diff --git a/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt b/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt deleted file mode 100644 index 801607bbe..000000000 --- a/src/main/kotlin/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.pubnub.api.models.server.access_manager.v3 - -class GrantTokenRequestBody { -} \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt b/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt index 76ec6e872..0dbd6033e 100644 --- a/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt +++ b/src/main/kotlin/com/pubnub/api/services/AccessManagerService.kt @@ -1,10 +1,11 @@ package com.pubnub.api.services -import com.google.gson.JsonObject import com.pubnub.api.models.server.Envelope import com.pubnub.api.models.server.access_manager.AccessManagerGrantPayload import retrofit2.Call -import retrofit2.http.* +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.QueryMap interface AccessManagerService { @@ -14,11 +15,4 @@ interface AccessManagerService { @QueryMap options: Map ): Call> - @POST("/v3/pam/{subKey}/grant") - fun grantToken( - @Path("subKey") subKey: String, - @Body body: Any, - @QueryMap options: Map - ): Call - } \ No newline at end of file From 9f552622e5f1914ded09ac000f92b9285d3b7701 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:27:08 +0200 Subject: [PATCH 72/98] Improve Subscribe APIs --- src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt index 5e5bf867e..7c61942eb 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Subscribe.kt @@ -7,7 +7,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -class Subscribe(pubnub: PubNub) : Endpoint(pubnub) { +class Subscribe internal constructor(pubnub: PubNub) : Endpoint(pubnub) { var channels = emptyList() var channelGroups = emptyList() @@ -58,7 +58,7 @@ class Subscribe(pubnub: PubNub) : Endpoint } override fun createResponse(input: Response): SubscribeEnvelope? { - return input.body() + return input.body()!! } override fun operationType() = PNOperationType.PNSubscribeOperation From 9743a8e297198d6b52de09ba4521aed7cf01ae05 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:27:52 +0200 Subject: [PATCH 73/98] Improve Mobile Push APIs --- .../com/pubnub/api/endpoints/push/AddChannelsToPush.kt | 7 +++---- .../com/pubnub/api/endpoints/push/ListPushProvisions.kt | 4 +--- .../api/endpoints/push/RemoveAllPushChannelsForDevice.kt | 6 +++--- .../pubnub/api/endpoints/push/RemoveChannelsFromPush.kt | 6 +++--- src/main/kotlin/com/pubnub/api/services/PushService.kt | 8 ++++---- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt index 59c23945e..31063213b 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/AddChannelsToPush.kt @@ -9,7 +9,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -class AddChannelsToPush(pubnub: PubNub) : Endpoint, PNPushAddChannelResult>(pubnub) { +class AddChannelsToPush(pubnub: PubNub) : Endpoint(pubnub) { lateinit var pushType: PNPushType lateinit var channels: List @@ -37,7 +37,7 @@ class AddChannelsToPush(pubnub: PubNub) : Endpoint, PNPushAddChannelRe } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { queryParams["add"] = channels.toCsv() if (pushType != PNPushType.APNS2) { @@ -63,8 +63,7 @@ class AddChannelsToPush(pubnub: PubNub) : Endpoint, PNPushAddChannelRe } - // is there any usable data in the response? - override fun createResponse(input: Response>): PNPushAddChannelResult? { + override fun createResponse(input: Response): PNPushAddChannelResult? { return PNPushAddChannelResult() } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt index 9607c2117..04142acc3 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/ListPushProvisions.kt @@ -59,9 +59,7 @@ class ListPushProvisions(pubnub: PubNub) : Endpoint, PNPushListProv } override fun createResponse(input: Response>): PNPushListProvisionsResult? { - return PNPushListProvisionsResult().apply { - channels = input.body()!! - } + return PNPushListProvisionsResult(input.body()!!) } override fun operationType() = PNOperationType.PNPushNotificationEnabledChannelsOperation diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt index 952ad080f..99832820a 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.kt @@ -12,7 +12,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -class RemoveAllPushChannelsForDevice(pubnub: PubNub) : Endpoint, PNPushRemoveAllChannelsResult>(pubnub) { +class RemoveAllPushChannelsForDevice(pubnub: PubNub) : Endpoint(pubnub) { lateinit var pushType: PNPushType lateinit var deviceId: String @@ -34,7 +34,7 @@ class RemoveAllPushChannelsForDevice(pubnub: PubNub) : Endpoint, PNPus } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { if (pushType != PNPushType.APNS2) { queryParams["type"] = pushType.toParamString() @@ -58,7 +58,7 @@ class RemoveAllPushChannelsForDevice(pubnub: PubNub) : Endpoint, PNPus } - override fun createResponse(input: Response>): PNPushRemoveAllChannelsResult? { + override fun createResponse(input: Response): PNPushRemoveAllChannelsResult? { return PNPushRemoveAllChannelsResult() } diff --git a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt index 15bc20fb8..71d9ea98b 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.kt @@ -9,7 +9,7 @@ import retrofit2.Call import retrofit2.Response import java.util.* -class RemoveChannelsFromPush(pubnub: PubNub) : Endpoint, PNPushRemoveChannelResult>(pubnub) { +class RemoveChannelsFromPush(pubnub: PubNub) : Endpoint(pubnub) { lateinit var pushType: PNPushType lateinit var channels: List @@ -37,7 +37,7 @@ class RemoveChannelsFromPush(pubnub: PubNub) : Endpoint, PNPushRemoveC } } - override fun doWork(queryParams: HashMap): Call> { + override fun doWork(queryParams: HashMap): Call { queryParams["remove"] = channels.toCsv() if (pushType != PNPushType.APNS2) { @@ -63,7 +63,7 @@ class RemoveChannelsFromPush(pubnub: PubNub) : Endpoint, PNPushRemoveC } - override fun createResponse(input: Response>): PNPushRemoveChannelResult? { + override fun createResponse(input: Response): PNPushRemoveChannelResult? { return PNPushRemoveChannelResult() } diff --git a/src/main/kotlin/com/pubnub/api/services/PushService.kt b/src/main/kotlin/com/pubnub/api/services/PushService.kt index 33682b2e0..39f0ce67c 100644 --- a/src/main/kotlin/com/pubnub/api/services/PushService.kt +++ b/src/main/kotlin/com/pubnub/api/services/PushService.kt @@ -12,14 +12,14 @@ interface PushService { @Path("subKey") subKey: String, @Path("pushToken") pushToken: String, @QueryMap options: Map - ): Call> + ): Call @GET("v1/push/sub-key/{subKey}/devices/{pushToken}/remove") fun removeAllChannelsForDevice( @Path("subKey") subKey: String, @Path("pushToken") pushToken: String, @QueryMap options: Map - ): Call> + ): Call @GET("v1/push/sub-key/{subKey}/devices/{pushToken}") fun listChannelsForDevice( @@ -36,7 +36,7 @@ interface PushService { @Path("subKey") subKey: String, @Path("deviceApns2") deviceApns2: String, @QueryMap options: Map - ): Call> + ): Call @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}") fun listChannelsForDeviceApns2( @@ -50,5 +50,5 @@ interface PushService { @Path("subKey") subKey: String, @Path("deviceApns2") deviceApns2: String, @QueryMap options: Map - ): Call> + ): Call } \ No newline at end of file From 940767f25324806172a876c6d88c3647d561bb82 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:29:02 +0200 Subject: [PATCH 74/98] Improve MessageCount APIs --- .../com/pubnub/api/endpoints/MessageCounts.kt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt b/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt index 42f0dfc61..9d92672f1 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/MessageCounts.kt @@ -45,18 +45,12 @@ class MessageCounts(pubnub: PubNub) : Endpoint): PNMessageCountResult? { val channelsMap = HashMap() - if (pubnub.mapper.getField(input.body()!!, "channels") != null) { - val it = pubnub.mapper.getObjectIterator(input.body()!!, "channels") - while (it.hasNext()) { - val entry = it.next() - channelsMap[entry.key] = entry.value.asLong - } - return PNMessageCountResult(channelsMap) - } else { - throw PubNubException(PubNubError.HTTP_ERROR).apply { - errorMessage = "History is disabled" - } + val it = pubnub.mapper.getObjectIterator(input.body()!!, "channels") + while (it.hasNext()) { + val entry = it.next() + channelsMap[entry.key] = entry.value.asLong } + return PNMessageCountResult(channelsMap) } override fun operationType() = PNOperationType.PNMessageCountOperation From 019fe01930cb228cc118ac6116cc5f1a18a19fd4 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:29:38 +0200 Subject: [PATCH 75/98] Improve DeleteMessage APIs --- .../kotlin/com/pubnub/api/endpoints/DeleteMessages.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt b/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt index 49c336357..10cb19bdc 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/DeleteMessages.kt @@ -3,12 +3,11 @@ package com.pubnub.api.endpoints import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.history.PNDeleteMessagesResult -import com.pubnub.api.models.server.DeleteMessagesEnvelope import retrofit2.Call import retrofit2.Response import java.util.* -class DeleteMessages(pubnub: PubNub) : Endpoint(pubnub) { +class DeleteMessages(pubnub: PubNub) : Endpoint(pubnub) { lateinit var channels: List var start: Long? = null @@ -21,7 +20,7 @@ class DeleteMessages(pubnub: PubNub) : Endpoint): Call { + override fun doWork(queryParams: HashMap): Call { start?.let { queryParams["start"] = it.toString().toLowerCase(Locale.US) } @@ -36,10 +35,7 @@ class DeleteMessages(pubnub: PubNub) : Endpoint): PNDeleteMessagesResult? { - if (input.body()!!.status != 200) { - throw PubNubException(PubNubError.PARSING_ERROR) - } + override fun createResponse(input: Response): PNDeleteMessagesResult? { return PNDeleteMessagesResult() } From d308fb1c4e68692883b18271dabceaf6ea06a099 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:30:23 +0200 Subject: [PATCH 76/98] Improve class models --- .../access_manager/PNAccessManagerGrantResults.kt | 4 ++-- .../channel_group/PNChannelGroupsResults.kt | 14 ++++++-------- .../message_actions/PNGetMessageActionsResult.kt | 8 +++----- .../models/consumer/presence/PNGetStateResult.kt | 2 +- .../api/models/consumer/presence/PNHereNow.kt | 8 ++++---- .../models/consumer/presence/PNSetStateResult.kt | 4 ++-- .../api/models/consumer/push/PNPushResults.kt | 7 +++---- .../access_manager/AccessManagerGrantPayload.kt | 4 ++-- 8 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt index 430dee193..e8b971f4a 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt @@ -6,8 +6,8 @@ class PNAccessManagerGrantResult( val level: String, val ttl: Int, val subscribeKey: String, - val channels: Map>, - val channelGroups: Map> + val channels: Map?>, + val channelGroups: Map?> ) class PNAccessManagerKeyData() { diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt index 31b524ed4..52667ba07 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsResults.kt @@ -6,12 +6,10 @@ class PNChannelGroupsDeleteGroupResult class PNChannelGroupsRemoveChannelResult -class PNChannelGroupsAllChannelsResult { - var channels = listOf() - internal set -} +class PNChannelGroupsAllChannelsResult internal constructor( + val channels: List +) -class PNChannelGroupsListAllResult { - var groups = listOf() - internal set -} \ No newline at end of file +class PNChannelGroupsListAllResult internal constructor( + val groups: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt index 905394f69..fed3e03fd 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.kt @@ -1,7 +1,5 @@ package com.pubnub.api.models.consumer.message_actions -class PNGetMessageActionsResult { - - var actions: List = emptyList() - internal set -} \ No newline at end of file +class PNGetMessageActionsResult internal constructor( + val actions: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt index a4c83e2b1..f1f213a8f 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNGetStateResult.kt @@ -2,6 +2,6 @@ package com.pubnub.api.models.consumer.presence import com.google.gson.JsonElement -data class PNGetStateResult( +class PNGetStateResult( val stateByUUID: Map ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt index fc49951ec..5019479b6 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNHereNow.kt @@ -2,19 +2,19 @@ package com.pubnub.api.models.consumer.presence import com.google.gson.JsonElement -data class PNHereNowResult( +class PNHereNowResult( val totalChannels: Int, val totalOccupancy: Int, - var channels: HashMap = hashMapOf() + val channels: HashMap = hashMapOf() ) -data class PNHereNowChannelData( +class PNHereNowChannelData( val channelName: String, val occupancy: Int, var occupants: List = emptyList() ) -data class PNHereNowOccupantData( +class PNHereNowOccupantData( val uuid: String, val state: JsonElement? = null ) diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt index 05b5ba784..aeb37d02b 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNSetStateResult.kt @@ -2,6 +2,6 @@ package com.pubnub.api.models.consumer.presence import com.google.gson.JsonElement -data class PNSetStateResult( - val state: JsonElement? +class PNSetStateResult( + val state: JsonElement ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt index 6c1baf1b2..63d4d8f13 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/push/PNPushResults.kt @@ -2,10 +2,9 @@ package com.pubnub.api.models.consumer.push class PNPushAddChannelResult -class PNPushListProvisionsResult { - var channels = listOf() - internal set -} +class PNPushListProvisionsResult internal constructor( + val channels: List +) class PNPushRemoveAllChannelsResult diff --git a/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt b/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt index 3531906b7..24c545138 100644 --- a/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt +++ b/src/main/kotlin/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.kt @@ -7,12 +7,12 @@ import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeysData class AccessManagerGrantPayload { - internal lateinit var level: String + internal var level: String? = null internal var ttl = 0 @SerializedName("subscribe_key") - internal lateinit var subscribeKey: String + internal var subscribeKey: String? = null internal val channels: Map? = null From 805c487f3be208b688c3ff262874af8b2c6a21a4 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:30:47 +0200 Subject: [PATCH 77/98] Cleanup --- src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 2 -- src/main/kotlin/com/pubnub/api/managers/MapperManager.kt | 3 ++- .../kotlin/com/pubnub/api/managers/SubscriptionManager.kt | 4 ++-- .../kotlin/com/pubnub/api/managers/TelemetryManager.kt | 8 +------- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt index 30516a957..4782441e9 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -109,8 +109,6 @@ class PubNubUtil { preparePamArguments("${request.url().encodedQuery()}×tamp=${timestamp}") } - println("encodedQueryString $encodedQueryString") - isV2Signature = !(requestURL.startsWith("/publish") && request.method().equals("post", ignoreCase = true)) if (!isV2Signature) { signatureBuilder.append(configuration.subscribeKey).append("\n") diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt index 7f0540d14..e995928b6 100644 --- a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -72,7 +72,8 @@ class MapperManager { fun getAsObject(element: JsonElement) = element.asJsonObject - fun getAsBoolean(element: JsonElement, field: String) = element.asJsonObject.get(field).asBoolean + fun getAsBoolean(element: JsonElement, field: String) = element.asJsonObject.get(field)?.asBoolean + .run { this != null } fun putOnObject(element: JsonObject, key: String, value: JsonElement) = element.add(key, value) diff --git a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt index 6a67467ee..70c7aa684 100644 --- a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt @@ -198,8 +198,8 @@ class SubscriptionManager(val pubnub: PubNub) { groups.addAll(heartbeatChannelGroups) heartbeatCall = Heartbeat(pubnub, channels, groups) heartbeatCall?.async { _, status -> - val heartbeatVerbosity: PNHeartbeatNotificationOptions = - pubnub.configuration.heartbeatNotificationOptions + val heartbeatVerbosity = pubnub.configuration.heartbeatNotificationOptions + if (status.error) { if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL || heartbeatVerbosity == PNHeartbeatNotificationOptions.FAILURES diff --git a/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt index 5598641e4..f959c1ac9 100644 --- a/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/TelemetryManager.kt @@ -6,12 +6,7 @@ import java.text.NumberFormat import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.MutableList -import kotlin.collections.forEach import kotlin.collections.set -import kotlin.collections.toList internal class TelemetryManager { @@ -27,8 +22,7 @@ internal class TelemetryManager { private val latencies: HashMap>> = HashMap() private val numberFormat by lazy { NumberFormat.getNumberInstance(Locale.US).apply { - maximumFractionDigits = - MAX_FRACTION_DIGITS + maximumFractionDigits = MAX_FRACTION_DIGITS roundingMode = RoundingMode.HALF_UP isGroupingUsed = false } From bc4ab7c8a08745d4de4ffe82b1fa07ba1da4466c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:31:24 +0200 Subject: [PATCH 78/98] Introduce Void response where applicable --- src/main/kotlin/com/pubnub/api/services/HistoryService.kt | 3 +-- .../kotlin/com/pubnub/api/services/MessageActionService.kt | 2 +- src/main/kotlin/com/pubnub/api/services/PresenceService.kt | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt index 61a1d155f..e27775a3c 100644 --- a/src/main/kotlin/com/pubnub/api/services/HistoryService.kt +++ b/src/main/kotlin/com/pubnub/api/services/HistoryService.kt @@ -1,7 +1,6 @@ package com.pubnub.api.services import com.google.gson.JsonElement -import com.pubnub.api.models.server.DeleteMessagesEnvelope import com.pubnub.api.models.server.FetchMessagesEnvelope import retrofit2.Call import retrofit2.http.DELETE @@ -23,7 +22,7 @@ interface HistoryService { @Path("subKey") subKey: String, @Path("channels") channels: String, @QueryMap options: Map - ): Call + ): Call @GET("v3/history/sub-key/{subKey}/channel/{channels}") fun fetchMessages( diff --git a/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt index f5f20b3dd..eed6422d7 100644 --- a/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt +++ b/src/main/kotlin/com/pubnub/api/services/MessageActionService.kt @@ -31,6 +31,6 @@ interface MessageActionService { @Path("messageTimetoken") messageTimetoken: String, @Path("actionTimetoken") actionTimetoken: String, @QueryMap options: Map - ): Call> + ): Call } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt index 2f8096855..703dc853c 100644 --- a/src/main/kotlin/com/pubnub/api/services/PresenceService.kt +++ b/src/main/kotlin/com/pubnub/api/services/PresenceService.kt @@ -15,14 +15,14 @@ interface PresenceService { @Path("subKey") subKey: String, @Path("channel") channel: String, @QueryMap options: Map - ): Call + ): Call @GET("v2/presence/sub-key/{subKey}/channel/{channel}/heartbeat") fun heartbeat( @Path("subKey") subKey: String, @Path("channel") channel: String, @QueryMap options: Map - ): Call> + ): Call @GET("v2/presence/sub-key/{subKey}/uuid/{uuid}") fun whereNow( From 9c54f0c4cfcd49ac094c162e057bbd64e3537cea Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:32:28 +0200 Subject: [PATCH 79/98] Improve integration tests --- .../com/pubnub/api/integration/AppTest.kt | 17 +++++------------ .../api/integration/BaseIntegrationTest.kt | 4 ++++ ...rationTest.kt => EncodingIntegrationTest.kt} | 10 ++-------- 3 files changed, 11 insertions(+), 20 deletions(-) rename src/test/kotlin/com/pubnub/api/integration/{PlayGroundIntegrationTest.kt => EncodingIntegrationTest.kt} (94%) diff --git a/src/test/kotlin/com/pubnub/api/integration/AppTest.kt b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt index 50c2cebed..f074aab7a 100644 --- a/src/test/kotlin/com/pubnub/api/integration/AppTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt @@ -17,7 +17,6 @@ import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResu import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult -import com.pubnub.api.printQueryParams import org.awaitility.Awaitility import org.awaitility.Durations import org.junit.jupiter.api.* @@ -56,10 +55,13 @@ class AppTest { ) } + @AfterEach + fun cleanUp() { + pubnub.forceDestroy() + } + @Test fun testPublishSync() { - val list = (0..9).map { it.toString() } + ('A'..'Z') - println(list) pubnub.publish().apply { channel = UUID.randomUUID().toString() message = UUID.randomUUID().toString() @@ -91,7 +93,6 @@ class AppTest { channel = UUID.randomUUID().toString() message = UUID.randomUUID().toString() }.async { result, status -> - status.printQueryParams() assertFalse(status.error) result!!.timetoken success.set(true) @@ -142,14 +143,6 @@ class AppTest { val time = SimpleDateFormat("HH:mm:ss:SSS").format(System.currentTimeMillis()) println("$time ${pnStatus.authKey} [$counter]") } - - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) val pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) diff --git a/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt index 4ebb248a6..ed6e743ce 100644 --- a/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt @@ -131,4 +131,8 @@ fun randomValue(length: Int = 10): String { .toList() .take(length) .joinToString(separator = "") +} + +fun randomNumeric(length: Int = 10): String { + return generateSequence { (0..9).random() }.take(length).toList().shuffled().joinToString(separator = "") } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt similarity index 94% rename from src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt rename to src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt index fe1676a55..9de6d0cae 100644 --- a/src/test/kotlin/com/pubnub/api/integration/PlayGroundIntegrationTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt @@ -18,8 +18,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.stream.Stream - -class PlayGroundIntegrationTest : BaseIntegrationTest() { +class EncodingIntegrationTest : BaseIntegrationTest() { @Test fun testSpecialCharsPathAndUrl() { @@ -73,8 +72,6 @@ class PlayGroundIntegrationTest : BaseIntegrationTest() { @ParameterizedTest(name = "testWith \"{0}\" {1}") @MethodSource("provideSpecialCharsStream") fun testVerifySignature(name: String, regular: String, encoded: String) { - println("testVerifySignature: $name $regular $encoded") - val success = AtomicBoolean() server.configuration.includeRequestIdentifier = false @@ -93,11 +90,9 @@ class PlayGroundIntegrationTest : BaseIntegrationTest() { queryParam = mapOf(propertyName to regular) shouldStore = true usePost = false - }.async { result, status -> + }.async { _, status -> assertFalse(status.error) - // val encodedParam = status.clientRequest!!.url().encodedQuery()!!.encodedParam(propertyName) val encodedParam = status.encodedParam(propertyName) - println("Checkking $name $encoded $encodedParam") assertEquals(encoded, encodedParam) decomposeAndVerifySignature(server.configuration, status.clientRequest!!) success.set(true) @@ -143,7 +138,6 @@ class PlayGroundIntegrationTest : BaseIntegrationTest() { historyTest.invoke() } - companion object { @JvmStatic From 24f1e9a598a4b76a1ce53fe90ad251c11974db89 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:33:06 +0200 Subject: [PATCH 80/98] Update legacy mock tests --- .../kotlin/com/pubnub/api/legacy/BaseTest.kt | 7 +- .../endpoints/DeleteMessagesEndpointTest.kt | 5 +- .../api/legacy/endpoints/EndpointTest.kt | 128 +++++- .../endpoints/access/GrantEndpointTest.kt | 1 - .../AllChannelsChannelGroupEndpointTest.kt | 2 - .../DeleteChannelGroupEndpointTest.kt | 11 +- .../ListAllChannelGroupEndpointTest.kt | 2 +- .../endpoints/history/MessageCountTest.kt | 28 -- .../AddMessageActionEndpointTest.kt | 1 - .../GetMessageActionEndpointTest.kt | 1 - .../RemoveMessageActionEndpointTest.kt | 23 +- .../endpoints/presence/HereNowEndpointTest.kt | 5 +- .../presence/SetStateEndpointTest.kt | 2 +- .../presence/WhereNowEndpointTest.kt | 6 +- .../legacy/endpoints/pubsub/PublishTest.kt | 5 +- .../api/legacy/endpoints/pubsub/SignalTest.kt | 3 - .../endpoints/push/AddChannelsToPushTest.kt | 2 +- .../endpoints/push/ListPushProvisionsTest.kt | 2 - .../managers/SubscriptionManagerTest.kt | 390 +++++------------- 19 files changed, 247 insertions(+), 377 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt index 50d9b8794..5d57c5d56 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt @@ -6,6 +6,7 @@ import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach abstract class BaseTest { @@ -30,8 +31,12 @@ abstract class BaseTest { @AfterEach fun afterEach() { wireMockServer.stop() - // assertTrue(wireMockServer.findAllUnmatchedRequests().isEmpty()) + wireMockServer.findAllUnmatchedRequests().forEach { + println("Unmatched ${it.url}") + } + assertTrue(wireMockServer.findAllUnmatchedRequests().isEmpty()) onAfter() + pubnub.forceDestroy() } open fun onBefore() { diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt index 9a66f6249..4dc7adb17 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/DeleteMessagesEndpointTest.kt @@ -87,10 +87,8 @@ class DeleteMessagesEndpointTest : BaseTest() { pubnub.deleteMessages().apply { channels = listOf("mychannel,my_channel") }.sync()!! - failTest() } catch (e: Exception) { - e.printStackTrace() - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -116,7 +114,6 @@ class DeleteMessagesEndpointTest : BaseTest() { pubnub.deleteMessages().apply { channels = listOf("mychannel,my_channel") }.async { result, status -> - println(status) result!! assertFalse(status.error) assertEquals(PNOperationType.PNDeleteMessagesOperation, status.operation) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt index 2eea138f8..262d3446c 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt @@ -1,8 +1,15 @@ package com.pubnub.api.legacy.endpoints +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.matching.UrlPattern +import com.google.gson.JsonArray +import com.google.gson.JsonObject import com.pubnub.api.Endpoint import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.legacy.BaseTest +import com.pubnub.api.listen +import com.pubnub.api.param import okhttp3.Request import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -10,6 +17,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.* +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference class EndpointTest : BaseTest() { @@ -82,7 +90,6 @@ class EndpointTest : BaseTest() { pubnub.configuration.uuid = expectedUuid fakeEndpoint { - println(it) assertEquals(expectedUuid, it["uuid"]) }.sync() } @@ -90,7 +97,6 @@ class EndpointTest : BaseTest() { @Test fun testQueryParam() { fakeEndpoint { - println(it) assertEquals("sf", it["city"]) assertEquals(pubnub.configuration.uuid, it["uuid"]) assertEquals(4, it.size) @@ -127,6 +133,124 @@ class EndpointTest : BaseTest() { }.sync() } + @Test + fun testErrorAffectedChannelsAndChannelGroups() { + stubFor( + any(UrlPattern.ANY) + .willReturn( + aResponse() + .withStatus(400) + .withBody( + JsonObject().apply { + add("payload", JsonObject().apply { + add("channels", JsonArray().apply { add("ch1");add("ch2") }) + add("channel-groups", JsonArray().apply { add("cg1") }) + }) + }.toString() + ) + ) + ) + + val success = AtomicBoolean() + + pubnub.time() + .async { result, status -> + assertEquals(listOf("ch1", "ch2"), status.affectedChannels) + assertEquals(listOf("cg1"), status.affectedChannelGroups) + success.set(true) + } + + success.listen() + } + + @Test + fun testNoAffectedChannelsAndChannelGroups() { + stubFor( + any(UrlPattern.ANY) + .willReturn( + aResponse() + .withStatus(400) + .withBody("""{}""") + ) + ) + + val success = AtomicBoolean() + + pubnub.time() + .async { _, status -> + assertTrue(status.affectedChannels.isEmpty()) + assertTrue(status.affectedChannelGroups.isEmpty()) + success.set(true) + } + + success.listen() + } + + @Test + fun testNoSecretKeySignatureParam() { + pubnub.configuration.secretKey = "" + + stubFor( + any(UrlPattern.ANY).willReturn( + aResponse().withBody("""[100]""") + ) + ) + + val success = AtomicBoolean() + + pubnub.time() + .async { _, status -> + assertNull(status.param("signature")) + success.set(true) + } + + success.listen() + } + + @Test + fun testSecretKeySignatureParam() { + pubnub.configuration.secretKey = "mySecretKey" + + stubFor( + any(UrlPattern.ANY).willReturn( + aResponse().withBody("""[100]""") + ) + ) + + val success = AtomicBoolean() + + pubnub.time() + .async { _, status -> + assertNotNull(status.param("signature")) + success.set(true) + } + + success.listen() + } + + + @Test + fun testUnauthorized() { + stubFor( + any(UrlPattern.ANY) + .willReturn( + forbidden() + ) + ) + + val success = AtomicBoolean() + + pubnub.time() + .async { _, status -> + assertTrue(status.error) + assertEquals(PNStatusCategory.PNAccessDeniedCategory, status.category) + success.set(true) + } + + success.listen() + } + + private fun fakeEndpoint( paramsCondition: (map: HashMap) -> Unit ) = object : Endpoint(pubnub) { diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt index 3f427e4f7..84ea1e2da 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt @@ -1654,7 +1654,6 @@ class GrantEndpointTest : BaseTest() { assertEquals(0, grantResult.channels.size) assertEquals(0, grantResult.channelGroups.size) } catch (e: PubNubException) { - e.printStackTrace() failTest() } } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt index a6f1b8c0e..773ab212a 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.kt @@ -130,7 +130,6 @@ class AllChannelsChannelGroupEndpointTest : BaseTest() { pubnub.listChannelsForChannelGroup().apply { channelGroup = "groupA" }.async { result, status -> - println(status) assertFalse(status.error) assertEquals(PNOperationType.PNChannelsForGroupOperation, status.operation) assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) @@ -154,7 +153,6 @@ class AllChannelsChannelGroupEndpointTest : BaseTest() { }.sync()!! throw RuntimeException() } catch (e: PubNubException) { - e.printStackTrace() assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) } } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt index a52a275b2..1ab001fbb 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/DeleteChannelGroupEndpointTest.kt @@ -87,14 +87,9 @@ class DeleteChannelGroupEndpointTest : BaseTest() { .willReturn(noContent()) ) - try { - pubnub.deleteChannelGroup().apply { - channelGroup = "groupA" - }.sync()!! - failTest() - } catch (e: PubNubException) { - assertPnException(PubNubError.PARSING_ERROR, e) - } + pubnub.deleteChannelGroup().apply { + channelGroup = "groupA" + }.sync()!! } @Test diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt index 93d5264bf..961d8ed80 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt @@ -44,7 +44,7 @@ class ListAllChannelGroupEndpointTest : BaseTest() { ) val response = pubnub.listAllChannelGroups().sync()!! - assertThat(response.groups, Matchers.contains("a", "b")) + assertThat(response.groups, Matchers.contains("a", "b"))//todo matchers } @Test diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt index 70741fa17..88d986c52 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt @@ -9,34 +9,6 @@ import org.junit.jupiter.api.Test class MessageCountTest : BaseTest() { - @Test - fun testSyncDisabled() { - val payload = - """ - [ - "Use of the history API requires the Storage & Playback which is not enabled - for this subscribe key.Login to your PubNub Dashboard Account and enable Storage & Playback. - Contact support @pubnub.com if you require further assistance.", - 0, - 0 - ] - """.trimIndent() - - stubFor( - get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) - .willReturn(aResponse().withBody(payload)) - ) - - try { - pubnub.messageCounts().apply { - channels = listOf("my_channel") - channelsTimetoken = listOf(10000L) - }.sync() - } catch (ex: PubNubException) { - assertEquals("History is disabled", ex.errorMessage) - } - } - @Test @Throws(PubNubException::class) fun testSingleChannelWithSingleToken() { diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt index f968728de..b2641895d 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt @@ -412,7 +412,6 @@ class AddMessageActionEndpointTest : BaseTest() { messageTimetoken = 123 ) }.async { _, status -> - println(status) assertFalse(status.error) assertEquals(PNOperationType.PNAddMessageAction, status.operation) telemetryParamName = "l_${status.operation.queryParam}" diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt index 3b050741a..5fdd32599 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/GetMessageActionEndpointTest.kt @@ -466,7 +466,6 @@ class GetMessageActionEndpointTest : BaseTest() { pubnub.getMessageActions().apply { channel = "coolChannel" }.async { _, status -> - println(status) assertFalse(status.error) assertEquals(PNOperationType.PNGetMessageActions, status.operation) telemetryParamName = "l_${status.operation.queryParam}" diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt index cad4200db..292cf1a2e 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/RemoveMessageActionEndpointTest.kt @@ -57,7 +57,7 @@ class RemoveMessageActionEndpointTest : BaseTest() { actionTimetoken = 100L }.async { _, status -> assertFalse(status.error) - assertEquals(PNOperationType.PNAddMessageAction, status.operation) + assertEquals(PNOperationType.PNDeleteMessageAction, status.operation) success.set(true) } @@ -73,7 +73,7 @@ class RemoveMessageActionEndpointTest : BaseTest() { """ { "status": 200 - "data": {} + "data": { } """.trimIndent() ) @@ -86,9 +86,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.sync()!! - failTest() } catch (e: Exception) { - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -106,9 +105,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.sync()!! - failTest() } catch (e: Exception) { - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -125,9 +123,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.sync()!! - failTest() } catch (e: Exception) { - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -152,9 +149,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.sync()!! - failTest() } catch (e: Exception) { - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -180,9 +176,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.sync()!! - failTest() } catch (e: Exception) { - assertPnException(PubNubError.PARSING_ERROR, e) + failTest() } } @@ -310,9 +305,8 @@ class RemoveMessageActionEndpointTest : BaseTest() { messageTimetoken = 123L actionTimetoken = 100L }.async { _, status -> - println(status) assertFalse(status.error) - assertEquals(PNOperationType.PNAddMessageAction, status.operation) + assertEquals(PNOperationType.PNDeleteMessageAction, status.operation) telemetryParamName = "l_${status.operation.queryParam}" assertEquals("l_msga", telemetryParamName) success.set(true) @@ -329,5 +323,4 @@ class RemoveMessageActionEndpointTest : BaseTest() { success.listen() } - } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt index 6092cb292..678a987b7 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/HereNowEndpointTest.kt @@ -1,10 +1,10 @@ package com.pubnub.api.legacy.endpoints.presence import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.legacy.BaseTest import com.pubnub.api.PubNubError.SUBSCRIBE_KEY_MISSING import com.pubnub.api.PubNubException import com.pubnub.api.enums.PNOperationType.PNHereNowOperation +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.presence.PNHereNowResult import org.awaitility.Awaitility import org.hamcrest.core.IsEqual @@ -273,11 +273,8 @@ class HereNowEndpointTest : BaseTest() { includeUUIDs = false }.sync()!! - println(response) - assertEquals(response.totalChannels, 1) assertEquals(response.totalOccupancy, 3) - println("response.channels: ${response.channels}") val requests = findAll(getRequestedFor(urlMatching("/.*"))) assertEquals(1, requests.size) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt index eeeb7fe75..95a54032b 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/SetStateEndpointTest.kt @@ -109,7 +109,7 @@ class SetStateEndpointTest : BaseTest() { state = mapOf("age" to 20) }.sync()!! - assertEquals(pubnub.mapper.elementToInt(result.state!!, "age"), 20) + assertEquals(pubnub.mapper.elementToInt(result.state, "age"), 20) assertEquals(pubnub.mapper.elementToString(result.state, "status"), "online") val requests = findAll(getRequestedFor(urlMatching("/.*"))) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt index bb64e4615..81a962046 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt @@ -1,11 +1,11 @@ package com.pubnub.api.legacy.endpoints.presence import com.github.tomakehurst.wiremock.client.WireMock.* -import com.pubnub.api.legacy.BaseTest import com.pubnub.api.PubNubError import com.pubnub.api.assertPnException import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.failTest +import com.pubnub.api.legacy.BaseTest import org.awaitility.Awaitility import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers @@ -207,7 +207,7 @@ class WhereNowEndpointTest : BaseTest() { val atomic = AtomicInteger(0) - pubnub.whereNow().async { result, status -> + pubnub.whereNow().async { _, status -> assertTrue(status.error) assertPnException(PubNubError.PARSING_ERROR, status) atomic.incrementAndGet() @@ -243,7 +243,7 @@ class WhereNowEndpointTest : BaseTest() { val atomic = AtomicInteger(0) - pubnub.whereNow().async { result, status -> + pubnub.whereNow().async { _, status -> assertTrue(status.error) assertPnException(PubNubError.HTTP_ERROR, status) assertEquals(PNStatusCategory.PNBadRequestCategory, status.category) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt index 2aacfc4ae..00926ba3a 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/PublishTest.kt @@ -1,7 +1,6 @@ package com.pubnub.api.legacy.endpoints.pubsub import com.github.tomakehurst.wiremock.client.WireMock.* -import com.google.gson.Gson import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException import com.pubnub.api.assertPnException @@ -351,8 +350,6 @@ class PublishTest : BaseTest() { .willReturn(aResponse().withBody("""[1,"Sent","15883272000000000"]""")) ) - println("pozho ${Gson().toJson(testPojo)}") - pubnub.publish().apply { channel = "coolChannel" message = testPojo @@ -464,7 +461,7 @@ class PublishTest : BaseTest() { pubnub.publish().apply { - }.async { result, status -> + }.async { _, status -> if (status.operation === PNOperationType.PNPublishOperation) { atomic.incrementAndGet() } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt index 150d93efa..a5865e21a 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt @@ -1,7 +1,6 @@ package com.pubnub.api.legacy.endpoints.pubsub import com.github.tomakehurst.wiremock.client.WireMock.* -import com.google.gson.Gson import com.pubnub.api.* import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNOperationType @@ -87,7 +86,6 @@ class SignalTest : BaseTest() { channel = "coolChannel" message = payload }.async { result, status -> - println(status) result!! assertFalse(status.error) assertEquals(PNOperationType.PNSignalOperation, status.operation) @@ -137,7 +135,6 @@ class SignalTest : BaseTest() { pubnub.addListener(object : SubscribeCallback() { override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { - println("rezz ${Gson().toJson(pnSignalResult)}") assertEquals("coolChannel", pnSignalResult.channel) assertEquals("hello", pnSignalResult.message.asString) assertEquals("uuid", pnSignalResult.publisher) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt index 24a5af84b..addde449c 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/AddChannelsToPushTest.kt @@ -130,7 +130,7 @@ class AddChannelsToPushTest : BaseTest() { deviceId = "niceDevice" pushType = PNPushType.FCM channels = listOf("ch1", "ch2", "ch3") - }.async { result, status -> + }.async { _, status -> Assertions.assertFalse(status.error) Assertions.assertEquals(PNOperationType.PNAddPushNotificationsOnChannelsOperation, status.operation) Assertions.assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt index 4a356768a..1ea708f96 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/ListPushProvisionsTest.kt @@ -157,7 +157,6 @@ class ListPushProvisionsTest : BaseTest() { }.sync()!! failTest() } catch (e: Exception) { - e.printStackTrace() assertPnException(PubNubError.PARSING_ERROR, e) } } @@ -192,7 +191,6 @@ class ListPushProvisionsTest : BaseTest() { deviceId = "niceDevice" pushType = PNPushType.FCM }.async { _, status -> - println(status) assertPnException(PubNubError.PARSING_ERROR, status) assertEquals(PNStatusCategory.PNMalformedResponseCategory, status.category) success.set(true) diff --git a/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt index b0f164535..1e561f701 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt @@ -2,9 +2,7 @@ package com.pubnub.api.legacy.managers import com.github.tomakehurst.wiremock.client.WireMock.* import com.google.gson.reflect.TypeToken -import com.pubnub.api.PubNub -import com.pubnub.api.PubNubException -import com.pubnub.api.PubNubUtil +import com.pubnub.api.* import com.pubnub.api.callbacks.SubscribeCallback import com.pubnub.api.enums.PNHeartbeatNotificationOptions import com.pubnub.api.enums.PNOperationType @@ -13,12 +11,6 @@ import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult -import com.pubnub.api.models.consumer.pubsub.PNSignalResult -import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult -import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult -import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult -import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult -import com.pubnub.api.toCsv import org.awaitility.Awaitility import org.hamcrest.Matchers import org.hamcrest.core.IsEqual @@ -128,11 +120,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessages.addAndGet(1) } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) Awaitility.await() @@ -194,11 +181,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessages.addAndGet(1) } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) Awaitility.await() @@ -252,6 +234,11 @@ class SubscriptionManagerTest : BaseTest() { @Test fun testPubNubUnsubscribeAll() { + stubFor( + get(urlPathMatching("/v2/subscribe/mySubscribeKey/.*")) + .willReturn(emptyJson()) + ) + pubnub.subscribe().apply { channels = listOf("ch1", "ch2") channelGroups = listOf("cg1", "cg2") @@ -334,12 +321,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessage.set(true) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -367,7 +348,7 @@ class SubscriptionManagerTest : BaseTest() { """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -416,18 +397,18 @@ class SubscriptionManagerTest : BaseTest() { ) ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("5")) + .willReturn(emptyJson()) + ) + pubnub.addListener(object : SubscribeCallback() { override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { gotMessages.addAndGet(1) } override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -453,7 +434,7 @@ class SubscriptionManagerTest : BaseTest() { """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -502,18 +483,18 @@ class SubscriptionManagerTest : BaseTest() { ) ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("5")) + .willReturn(emptyJson()) + ) + pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { gotMessages.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -540,7 +521,7 @@ class SubscriptionManagerTest : BaseTest() { """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -608,18 +589,18 @@ class SubscriptionManagerTest : BaseTest() { ) ) + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("5")) + .willReturn(notFound()) + ) + pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { gotMessages.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -681,13 +662,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -749,13 +723,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -817,13 +784,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -904,13 +864,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -962,13 +915,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1035,12 +981,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessage.set(true) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1097,7 +1037,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) assertTrue(requests.size >= 1) @@ -1106,11 +1045,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessage.set(true) } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1173,7 +1107,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) assertTrue(requests.size >= 1) @@ -1182,11 +1115,6 @@ class SubscriptionManagerTest : BaseTest() { gotMessage.set(true) } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1307,11 +1235,18 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("20")) + .willReturn(emptyJson()) + ) + pubnub.addListener(object : SubscribeCallback() { - override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + } + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { - val requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))) - assertTrue(requests.size >= 1) when (pnMessageResult.message.asJsonObject["text"].asString) { "Message" -> { gotMessage1.set(true) @@ -1324,13 +1259,6 @@ class SubscriptionManagerTest : BaseTest() { } } } - - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1395,12 +1323,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1467,12 +1389,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1492,10 +1408,12 @@ class SubscriptionManagerTest : BaseTest() { val expectedPayload = PubNubUtil.urlDecode( """%7B%22ch1%22%3A%5B%22p1%22%2C%22p2%22%5D%2C%22cg2%22%3A%5B%22p1%22%2C%22p2%22%5D%7D""" ) + val expectedMap = pubnub.mapper.fromJson?>( expectedPayload, object : TypeToken?>() {}.type ) + stubFor( get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) .willReturn( @@ -1528,6 +1446,17 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(emptyJson()) + ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .willReturn(emptyJson()) + ) + pubnub.configuration.presenceTimeout = 20 pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL @@ -1569,16 +1498,8 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) - pubnub.subscribe().apply { channels = listOf("ch1", "ch2") channelGroups = listOf("cg1", "cg2") @@ -1642,12 +1563,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1710,12 +1625,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1745,7 +1654,7 @@ class SubscriptionManagerTest : BaseTest() { """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -1770,6 +1679,13 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("5")) + .willReturn(notFound()) + ) + pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { @@ -1779,12 +1695,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.set(true) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1836,12 +1746,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1902,12 +1806,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -1964,12 +1862,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2025,7 +1917,6 @@ class SubscriptionManagerTest : BaseTest() { ) pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (atomic.get() == 0) { assertEquals(true, pnPresenceEventResult.hereNowRefresh) @@ -2034,11 +1925,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2093,7 +1979,6 @@ class SubscriptionManagerTest : BaseTest() { ) pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (atomic.get() == 0) { val joinList: MutableList = ArrayList() @@ -2106,11 +1991,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2165,7 +2045,6 @@ class SubscriptionManagerTest : BaseTest() { ) pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (atomic.get() == 0) { val leaveList: MutableList = ArrayList() @@ -2178,11 +2057,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2237,7 +2111,6 @@ class SubscriptionManagerTest : BaseTest() { ) pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (atomic.get() == 0) { val timeoutList = listOf( @@ -2251,11 +2124,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2308,7 +2176,6 @@ class SubscriptionManagerTest : BaseTest() { pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (atomic.get() == 0) { assertEquals("join", pnPresenceEventResult.event) @@ -2319,11 +2186,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2395,7 +2257,6 @@ class SubscriptionManagerTest : BaseTest() { ) pubnub.addListener(object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (pnPresenceEventResult.event == "state-change") { if (pnPresenceEventResult.state!!.asJsonObject.has("state") && @@ -2406,11 +2267,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2470,12 +2326,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -2490,6 +2340,11 @@ class SubscriptionManagerTest : BaseTest() { @Test fun testRemoveListener() { + stubFor( + get(urlPathMatching("/v2/subscribe/mySubscribeKey/.*")) + .willReturn(emptyJson()) + ) + val atomic = AtomicInteger(0) val sub1: SubscribeCallback = object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) { @@ -2504,11 +2359,6 @@ class SubscriptionManagerTest : BaseTest() { atomic.addAndGet(1) } - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2633,12 +2483,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2719,13 +2563,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2767,13 +2604,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } assertNotNull(sub1) pubnub.addListener(sub1) @@ -2816,13 +2646,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2845,13 +2668,13 @@ class SubscriptionManagerTest : BaseTest() { pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.FAILURES stubFor( - get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) .willReturn( aResponse().withBody( """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -2876,6 +2699,18 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(aResponse().withStatus(404).withBody("{}")) + ) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .withQueryParam("tt", matching("5")) + .willReturn(notFound()) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) { if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { @@ -2883,13 +2718,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2912,13 +2740,14 @@ class SubscriptionManagerTest : BaseTest() { pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL stubFor( - get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .withQueryParam("tt", matching("0")) .willReturn( aResponse().withBody( """ { "t": { - "t": "14607577960932487", + "t": "5", "r": 1 }, "m": [ @@ -2944,6 +2773,17 @@ class SubscriptionManagerTest : BaseTest() { ) ) + stubFor( + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(emptyJson().withStatus(403)) + ) + + stubFor( + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .withQueryParam("tt", matching("5")) + .willReturn(notFound()) + ) + val sub1: SubscribeCallback = object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) { if (pnStatus.operation == PNOperationType.PNHeartbeatOperation && pnStatus.error) { @@ -2951,13 +2791,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -2978,7 +2811,7 @@ class SubscriptionManagerTest : BaseTest() { pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.NONE stubFor( - get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) .willReturn( aResponse().withBody( """ @@ -3015,13 +2848,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -3043,7 +2869,7 @@ class SubscriptionManagerTest : BaseTest() { pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.NONE stubFor( - get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) .willReturn( aResponse().withBody( """ @@ -3074,6 +2900,7 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + stubFor( get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) .willReturn( @@ -3089,6 +2916,7 @@ class SubscriptionManagerTest : BaseTest() { ) ) ) + val sub1: SubscribeCallback = object : SubscribeCallback() { override fun status(pubnub: PubNub, pnStatus: PNStatus) { if (pnStatus.operation != PNOperationType.PNHeartbeatOperation) { @@ -3096,13 +2924,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) @@ -3173,13 +2994,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -3256,13 +3070,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) pubnub.subscribe().apply { @@ -3417,13 +3224,6 @@ class SubscriptionManagerTest : BaseTest() { } } - override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} - override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } pubnub.addListener(sub1) From bf8d3e003ff7d3ca3c7e7469d1008023e5d4d1f1 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:33:39 +0200 Subject: [PATCH 81/98] Decrease waiting time for async tests --- src/test/kotlin/com/pubnub/api/Utils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/com/pubnub/api/Utils.kt b/src/test/kotlin/com/pubnub/api/Utils.kt index 628e250ce..6b7b60a20 100644 --- a/src/test/kotlin/com/pubnub/api/Utils.kt +++ b/src/test/kotlin/com/pubnub/api/Utils.kt @@ -20,7 +20,7 @@ import java.util.stream.Collectors private fun observe(success: AtomicBoolean) { Awaitility.await() - .atMost(Durations.FIVE_SECONDS) + .atMost(Durations.TWO_SECONDS) .with() .until(success::get) } @@ -173,3 +173,5 @@ fun getResourceFileAsString(fileName: String?): String? { } } } + +fun Any.stringify() = Gson().toJson(this) \ No newline at end of file From 6a6eb0a50cabe4b65b2a1529d764dee2f393e33f Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:34:13 +0200 Subject: [PATCH 82/98] Implement test suite for Channel Group APIs --- .../AddChannelChannelGroupTestSuite.kt | 51 +++++++++ .../AllChannelsChannelGroupTestSuite.kt | 101 ++++++++++++++++++ .../DeleteChannelGroupTestSuite.kt | 50 +++++++++ .../ListAllChannelGroupTestSuite.kt | 58 ++++++++++ .../RemoveChannelChannelGroupTestSuite.kt | 43 ++++++++ 5 files changed, 303 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/channel_groups/AddChannelChannelGroupTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/channel_groups/AllChannelsChannelGroupTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/channel_groups/DeleteChannelGroupTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/channel_groups/ListAllChannelGroupTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/AddChannelChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/AddChannelChannelGroupTestSuite.kt new file mode 100644 index 000000000..21e3b0d1b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/AddChannelChannelGroupTestSuite.kt @@ -0,0 +1,51 @@ +package com.pubnub.api.suite.channel_groups + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.channel_groups.AddChannelChannelGroup +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAddChannelResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class AddChannelChannelGroupTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_cg" + + override fun pnOperation() = PNOperationType.PNAddChannelsToGroupOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): AddChannelChannelGroup { + return pubnub.addChannelsToChannelGroup().apply { + channelGroup = "cg1" + channels = listOf("ch1", "ch2") + } + } + + override fun verifyResultExpectations(result: PNChannelGroupsAddChannelResult) { + + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "service": "channel-registry", + "error": false + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/cg1")) + .withQueryParam("add", equalTo("ch1,ch2")) + } + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to listOf("cg1") + + override fun voidResponse() = true + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/AllChannelsChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/AllChannelsChannelGroupTestSuite.kt new file mode 100644 index 000000000..d39ef7574 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/AllChannelsChannelGroupTestSuite.kt @@ -0,0 +1,101 @@ +package com.pubnub.api.suite.channel_groups + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.channel_groups.AllChannelsChannelGroup +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAllChannelsResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class AllChannelsChannelGroupTestSuite : + EndpointTestSuite() { + + override fun telemetryParamName() = "l_cg" + + override fun pnOperation() = PNOperationType.PNChannelsForGroupOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): AllChannelsChannelGroup { + return pubnub.listChannelsForChannelGroup().apply { + channelGroup = "cg1" + } + } + + override fun verifyResultExpectations(result: PNChannelGroupsAllChannelsResult) { + assertEquals(listOf("ch1", "ch2"), result.channels) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "payload": { + "channels": [ + "ch1", + "ch2" + ], + "group": "cg1" + }, + "service": "channel-registry", + "error": false + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"payload":{"channels":null,"group":null}}""", + """{"payload":{"channels":null}}""", + """{"payload":{}}""", + """{"payload":null}""" + ) + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/cg1")) + } + + override fun affectedChannelsAndGroups() = emptyList() to listOf("cg1") + + override fun optionalScenarioList() = listOf( + OptionalScenario().apply { + responseBuilder = { + withBody( + """ + { + "payload": { + "channels": [], + "group": "cg1" + } + } + """.trimIndent() + ) + } + additionalChecks = { status: PNStatus, result: PNChannelGroupsAllChannelsResult? -> + assertFalse(status.error) + assertTrue(result!!.channels.isEmpty()) + } + }, + OptionalScenario().apply { + responseBuilder = { + withBody( + """ + { + "payload": { + "group": "cg1" + } + } + """.trimIndent() + ) + } + pnError = PubNubError.PARSING_ERROR + result = Result.FAIL + additionalChecks = { status: PNStatus, result: PNChannelGroupsAllChannelsResult? -> + assertTrue(status.error) + } + } + + ) + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/DeleteChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/DeleteChannelGroupTestSuite.kt new file mode 100644 index 000000000..fb8a6a51e --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/DeleteChannelGroupTestSuite.kt @@ -0,0 +1,50 @@ +package com.pubnub.api.suite.channel_groups + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.endpoints.channel_groups.DeleteChannelGroup +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class DeleteChannelGroupTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_cg" + + override fun pnOperation() = PNOperationType.PNRemoveGroupOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): DeleteChannelGroup { + return pubnub.deleteChannelGroup().apply { + channelGroup = "cg1" + } + } + + override fun verifyResultExpectations(result: PNChannelGroupsDeleteGroupResult) { + + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "service": "channel-registry", + "error": false + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/cg1/remove")) + } + + override fun affectedChannelsAndGroups() = emptyList() to listOf("cg1") + + override fun voidResponse() = true + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/ListAllChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/ListAllChannelGroupTestSuite.kt new file mode 100644 index 000000000..52c1924cb --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/ListAllChannelGroupTestSuite.kt @@ -0,0 +1,58 @@ +package com.pubnub.api.suite.channel_groups + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.endpoints.channel_groups.ListAllChannelGroup +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class ListAllChannelGroupTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_cg" + + override fun pnOperation() = PNOperationType.PNChannelGroupsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): ListAllChannelGroup { + return pubnub.listAllChannelGroups() + } + + override fun verifyResultExpectations(result: PNChannelGroupsListAllResult) { + assertEquals(listOf("cg1", "cg2"), result.groups) + } + + override fun successfulResponseBody() = """{"payload":{"groups":["cg1","cg2"]}}""" + + override fun unsuccessfulResponseBodyList() = listOf( + """{"payload":{"groups":null}}""", + """{"payload":{"groups":{}}}""", + """{"payload":{}}""", + """{"payload":null}""" + ) + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + } + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { + withBody("""{"payload":{"groups":[]}}""") + } + additionalChecks = { status: PNStatus, result: PNChannelGroupsListAllResult? -> + assertFalse(status.error) + assertTrue(result!!.groups.isEmpty()) + } + result = Result.SUCCESS + } + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt new file mode 100644 index 000000000..355030273 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt @@ -0,0 +1,43 @@ +package com.pubnub.api.suite.channel_groups + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.channel_groups.RemoveChannelChannelGroup +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveChannelChannelGroupTestSuite : + EndpointTestSuite() { + + override fun telemetryParamName() = "l_cg" + + override fun pnOperation() = PNOperationType.PNRemoveChannelsFromGroupOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveChannelChannelGroup { + return pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = "cg1" + channels = listOf("ch1", "ch2") + } + } + + override fun verifyResultExpectations(result: PNChannelGroupsRemoveChannelResult) {} + + override fun successfulResponseBody() = """{"payload":{"groups":["cg1","cg2"]}}""" + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/cg1")) + .withQueryParam("remove", equalTo("ch1,ch2")) + } + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to listOf("cg1") + + override fun voidResponse() = true + +} \ No newline at end of file From 55426a6ab89fb504b92cab5ffa589de8766c8398 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:34:27 +0200 Subject: [PATCH 83/98] Implement test suite for Grant APIs --- .../pubnub/api/suite/grant/GrantTestSuite.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/grant/GrantTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/grant/GrantTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/grant/GrantTestSuite.kt new file mode 100644 index 000000000..0c0632fff --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/grant/GrantTestSuite.kt @@ -0,0 +1,76 @@ +package com.pubnub.api.suite.grant + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.access.Grant +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SEC +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals + +class GrantTestSuite : EndpointTestSuite() { + + override fun onBefore() { + super.onBefore() + pubnub.configuration.secretKey = "mySecretKey" + } + + override fun telemetryParamName() = "l_pam"; + + override fun pnOperation() = PNOperationType.PNAccessManagerGrant + + override fun requiredKeys() = SUB + SEC + + override fun snippet(): Grant { + return pubnub.grant().apply { + + } + } + + override fun verifyResultExpectations(result: PNAccessManagerGrantResult) { + assertEquals("mySubscribeKey", result.subscribeKey) + assertEquals(0, result.channels.size) + assertEquals(0, result.channelGroups.size) + assertEquals("subkey", result.level) + assertEquals(1, result.ttl) + } + + override fun successfulResponseBody() = """ + { + "message": "Success", + "payload": { + "level": "subkey", + "subscribe_key": "mySubscribeKey", + "ttl": 1, + "r": 0, + "w": 0, + "m": 0, + "d": 0 + }, + "service": "Access Manager", + "status": 200 + } + """.trimIndent() + + + override fun unsuccessfulResponseBodyList() = listOf( + """{"payload":{}}""", + """{"payload":null}""" + ) + + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("r", equalTo("0")) + .withQueryParam("w", equalTo("0")) + .withQueryParam("d", equalTo("0")) + .withQueryParam("m", equalTo("0")) + .withQueryParam("ttl", equalTo("-1")) + .withQueryParam("signature", matching(".*")) + } + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + +} \ No newline at end of file From 2cbdfa9bd4967a7a01ad6b64ee25520a9b44250e Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:34:45 +0200 Subject: [PATCH 84/98] Implement test suite for Message Count APIs --- .../history/counts/MessageCountsTestSuite.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/counts/MessageCountsTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/history/counts/MessageCountsTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/counts/MessageCountsTestSuite.kt new file mode 100644 index 000000000..2e006eb3c --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/counts/MessageCountsTestSuite.kt @@ -0,0 +1,53 @@ +package com.pubnub.api.suite.history.counts + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.MessageCounts +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNMessageCountResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals + +class MessageCountsTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_mc" + + override fun pnOperation() = PNOperationType.PNMessageCountOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): MessageCounts { + return pubnub.messageCounts().apply { + channels = listOf("ch1") + channelsTimetoken = listOf(1588284000000) + } + } + + override fun verifyResultExpectations(result: PNMessageCountResult) { + assertEquals(1, result.channels.keys.size) + assertEquals(5L, result.channels["ch1"]) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "error": false, + "error_message": "", + "channels": { + "ch1": 5 + } + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/ch1")) + .withQueryParam("timetoken", equalTo("1588284000000")) + } + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + +} \ No newline at end of file From d2bc7ad803aed45d8f81da98e4da5f14236fe8c7 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:35:01 +0200 Subject: [PATCH 85/98] Implement test suite for History APIs --- .../suite/history/DeleteMessagesTestSuite.kt | 50 +++++++ .../history/{ => v2}/HistoryMetaTestSuite.kt | 21 +-- .../history/{ => v2}/HistoryTestSuite.kt | 17 +-- .../v3/FetchMessagesMetaActionsTestSuite.kt | 124 ++++++++++++++++++ .../history/v3/FetchMessagesTestSuite.kt | 99 ++++++++++++++ 5 files changed, 293 insertions(+), 18 deletions(-) create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/DeleteMessagesTestSuite.kt rename src/test/kotlin/com/pubnub/api/suite/history/{ => v2}/HistoryMetaTestSuite.kt (83%) rename src/test/kotlin/com/pubnub/api/suite/history/{ => v2}/HistoryTestSuite.kt (83%) create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/history/DeleteMessagesTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/DeleteMessagesTestSuite.kt new file mode 100644 index 000000000..ca2272175 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/DeleteMessagesTestSuite.kt @@ -0,0 +1,50 @@ +package com.pubnub.api.suite.history + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.DeleteMessages +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.history.PNDeleteMessagesResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class DeleteMessagesTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_hist" + + override fun pnOperation() = PNOperationType.PNDeleteMessagesOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): DeleteMessages { + return pubnub.deleteMessages().apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: PNDeleteMessagesResult) { + + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "error": false, + "error_message": "" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder { + return delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/ch1")) + .withQueryParam("start", absent()) + .withQueryParam("end", absent()) + } + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun voidResponse() = true + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryMetaTestSuite.kt similarity index 83% rename from src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryMetaTestSuite.kt index 7b5623071..03c3de5c4 100644 --- a/src/test/kotlin/com/pubnub/api/suite/history/HistoryMetaTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryMetaTestSuite.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.suite.history +package com.pubnub.api.suite.history.v2 import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.* @@ -7,6 +7,7 @@ import com.google.gson.reflect.TypeToken import com.pubnub.api.PubNubError import com.pubnub.api.endpoints.History import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.history.PNHistoryResult import com.pubnub.api.suite.* import org.junit.jupiter.api.Assertions.assertEquals @@ -22,15 +23,15 @@ class HistoryMetaTestSuite : EndpointTestSuite() { override fun snippet(): History { return pubnub.history().apply { - channel = "foo" + channel = "ch1" includeMeta = true includeTimetoken = true } } override fun verifyResultExpectations(result: PNHistoryResult) { - assertEquals(100, result.startTimetoken) - assertEquals(200, result.endTimetoken) + assertEquals(100L, result.startTimetoken) + assertEquals(200L, result.endTimetoken) assertEquals(2, result.messages.size) assertEquals("msg1", result.messages[0].entry.asString) assertEquals("msg2", result.messages[1].entry.asString) @@ -41,8 +42,8 @@ class HistoryMetaTestSuite : EndpointTestSuite() { object : TypeToken?>() {}.type ) ) - assertEquals(100, result.messages[0].timetoken) - assertEquals(200, result.messages[1].timetoken) + assertEquals(100L, result.messages[0].timetoken) + assertEquals(200L, result.messages[1].timetoken) } override fun successfulResponseBody() = """ @@ -73,7 +74,7 @@ class HistoryMetaTestSuite : EndpointTestSuite() { override fun mappingBuilder(): MappingBuilder { return get( - urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/foo") + urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/ch1") ) .withQueryParam("include_token", equalTo("true")) .withQueryParam("count", equalTo("100")) @@ -81,14 +82,14 @@ class HistoryMetaTestSuite : EndpointTestSuite() { .withQueryParam("reverse", equalTo("false")) } - override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() override fun optionalScenarioList() = listOf( - OptionalScenario().apply { + OptionalScenario().apply { responseBuilder = { withBody("""["First Element Not An Array",0,0]""") } - additionalChecks = { status -> + additionalChecks = { status: PNStatus, _: PNHistoryResult? -> assertTrue(status.error) assertEquals(status.exception!!.errorMessage, "History is disabled") } diff --git a/src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryTestSuite.kt similarity index 83% rename from src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryTestSuite.kt index f97f75bdd..6dc5ef3e4 100644 --- a/src/test/kotlin/com/pubnub/api/suite/history/HistoryTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/history/v2/HistoryTestSuite.kt @@ -1,10 +1,11 @@ -package com.pubnub.api.suite.history +package com.pubnub.api.suite.history.v2 import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.PubNubError import com.pubnub.api.endpoints.History import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.history.PNHistoryResult import com.pubnub.api.suite.* import org.junit.jupiter.api.Assertions.* @@ -19,13 +20,13 @@ class HistoryTestSuite : EndpointTestSuite() { override fun snippet(): History { return pubnub.history().apply { - channel = "foo" + channel = "ch1" } } override fun verifyResultExpectations(result: PNHistoryResult) { - assertEquals(100, result.startTimetoken) - assertEquals(200, result.endTimetoken) + assertEquals(100L, result.startTimetoken) + assertEquals(200L, result.endTimetoken) assertEquals(2, result.messages.size) assertEquals("msg1", result.messages[0].entry.asString) assertEquals("msg2", result.messages[1].entry.asString) @@ -44,7 +45,7 @@ class HistoryTestSuite : EndpointTestSuite() { override fun mappingBuilder(): MappingBuilder { return get( - urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/foo") + urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/ch1") ) .withQueryParam("include_token", equalTo("false")) .withQueryParam("count", equalTo("100")) @@ -52,14 +53,14 @@ class HistoryTestSuite : EndpointTestSuite() { .withQueryParam("reverse", equalTo("false")) } - override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() override fun optionalScenarioList() = listOf( - OptionalScenario().apply { + OptionalScenario().apply { responseBuilder = { withBody("""["First Element Not An Array",0,0]""") } - additionalChecks = { status -> + additionalChecks = { status: PNStatus, _: PNHistoryResult? -> assertTrue(status.error) assertEquals(status.exception!!.errorMessage, "History is disabled") } diff --git a/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt new file mode 100644 index 000000000..9d50e9856 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt @@ -0,0 +1,124 @@ +package com.pubnub.api.suite.history.v3 + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.JsonObject +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.FetchMessages +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class FetchMessagesMetaActionsTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_hist" + + override fun pnOperation() = PNOperationType.PNFetchMessagesOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): FetchMessages { + return pubnub.fetchMessages().apply { + channels = listOf("ch1") + includeMeta = true + includeMessageActions = true + } + } + + override fun verifyResultExpectations(result: PNFetchMessagesResult) { + assertEquals(1, result.channels.size) + assertTrue(result.channels.containsKey("ch1")) + assertEquals("bar", result.channels["ch1"]!![0].message.asString) + assertEquals( + JsonObject().apply { addProperty("color", "red") }, + result.channels["ch1"]!![0].meta + ) + assertEquals(100, result.channels["ch1"]!![0].timetoken) + + val actions = result.channels["ch1"]!![0].actions + assertEquals(1, actions!!.keys.size) + assertEquals(1, actions["reaction"]!!["smile"]!!.size) + assertEquals("publishersUuid", actions["reaction"]!!["smile"]!![0].uuid) + assertEquals("200", actions["reaction"]!!["smile"]!![0].actionTimetoken) + } + + override fun successfulResponseBody() = """ + { + "channels": { + "ch1": [ + { + "message": "bar", + "meta": { + "color": "red" + }, + "timetoken": 100, + "actions": { + "reaction": { + "smile": [ + { + "uuid": "publishersUuid", + "actionTimetoken": "200" + } + ] + } + } + } + ] + } + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """ + { + "channels": { + "ch1": [ + { + "timetoken": 100 + } + ] + } + } + """.trimIndent(), + """ + { + "channels": { + "ch1": [ + { + "entry": "hello", + "timetoken": 100 + } + ], + "ch2": [], + "ch3": null + } + } + """.trimIndent() + ) + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v3/history-with-actions/sub-key/mySubscribeKey/channel/ch1")) + .withQueryParam("max", equalTo("1")) + .withQueryParam("include_meta", equalTo("true")) + } + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("""{"channels":{"ch3":null}}""") } + result = Result.FAIL + pnError = PubNubError.PARSING_ERROR + additionalChecks = { status: PNStatus, result: PNFetchMessagesResult? -> + assertTrue(status.error) + assertNull(result) + assertEquals(PNStatusCategory.PNMalformedResponseCategory, status.category) + } + } + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesTestSuite.kt new file mode 100644 index 000000000..320b15716 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesTestSuite.kt @@ -0,0 +1,99 @@ +package com.pubnub.api.suite.history.v3 + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.FetchMessages +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class FetchMessagesTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_hist" + + override fun pnOperation() = PNOperationType.PNFetchMessagesOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): FetchMessages { + return pubnub.fetchMessages().apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: PNFetchMessagesResult) { + assertEquals(1, result.channels.size) + assertTrue(result.channels.containsKey("ch1")) + assertEquals("hello", result.channels["ch1"]!![0].message.asString) + assertEquals(100, result.channels["ch1"]!![0].timetoken) + assertNull(result.channels["ch1"]!![0].meta) + } + + override fun successfulResponseBody() = """ + { + "channels": { + "ch1": [ + { + "message": "hello", + "timetoken": 100 + } + ] + } + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """ + { + "channels": { + "ch1": [ + { + "timetoken": 100 + } + ] + } + } + """.trimIndent(), + """ + { + "channels": { + "ch1": [ + { + "entry": "hello", + "timetoken": 100 + } + ], + "ch2": [], + "ch3": null + } + } + """.trimIndent() + ) + + override fun mappingBuilder(): MappingBuilder { + return get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/ch1")) + .withQueryParam("max", equalTo("1")) + .withQueryParam("include_meta", absent()) + } + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("""{"channels":{"ch3":null}}""") } + result = Result.FAIL + pnError = PubNubError.PARSING_ERROR + additionalChecks = { status: PNStatus, result: PNFetchMessagesResult? -> + assertTrue(status.error) + assertNull(result) + assertEquals(PNStatusCategory.PNMalformedResponseCategory, status.category) + } + } + ) + } +} \ No newline at end of file From f324b265f21ec4812eb0e8b50014c0062b3de41b Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:35:19 +0200 Subject: [PATCH 86/98] Implement test suite for MessageAction APIs --- .../AddMessageActionTestSuite.kt} | 11 ++- .../GetMessageActionsMultipleTestSuite.kt | 77 +++++++++++++++++++ .../GetMessageActionsTestSuite.kt | 65 ++++++++++++++++ .../RemoveMessageActionsTestSuite.kt | 47 +++++++++++ 4 files changed, 196 insertions(+), 4 deletions(-) rename src/test/kotlin/com/pubnub/api/suite/{MessageActionTestSuite.kt => message_actions/AddMessageActionTestSuite.kt} (83%) create mode 100644 src/test/kotlin/com/pubnub/api/suite/message_actions/GetMessageActionsMultipleTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/message_actions/GetMessageActionsTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/message_actions/RemoveMessageActionsTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/message_actions/AddMessageActionTestSuite.kt similarity index 83% rename from src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/message_actions/AddMessageActionTestSuite.kt index 2d3975d4b..98b22bb74 100644 --- a/src/test/kotlin/com/pubnub/api/suite/MessageActionTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/message_actions/AddMessageActionTestSuite.kt @@ -1,13 +1,16 @@ -package com.pubnub.api.suite +package com.pubnub.api.suite.message_actions import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.endpoints.message_actions.AddMessageAction import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB import org.junit.jupiter.api.Assertions.assertEquals -class MessageActionTestSuite : EndpointTestSuite() { +class AddMessageActionTestSuite : EndpointTestSuite() { override fun telemetryParamName() = "l_msga" @@ -46,11 +49,11 @@ class MessageActionTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_msga" + + override fun pnOperation() = PNOperationType.PNGetMessageActions + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): GetMessageActions { + return pubnub.getMessageActions().apply { + channel = "ch1" + limit = 5 + start = 991 + end = 969 + } + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "100", + "type": "reaction", + "uuid": "abc", + "value": "๐Ÿ˜€", + "actionTimetoken": "970" + }, + { + "messageTimetoken": "100", + "type": "reaction", + "uuid": "abc", + "value": "๐Ÿ˜ฌ", + "actionTimetoken": "980" + }, + { + "messageTimetoken": "200", + "type": "action", + "uuid": "xyz", + "value": "๐Ÿ˜€", + "actionTimetoken": "990" + } + ] + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"status":200,"data":null}""", + """{"status":200}""" + ) + + override fun verifyResultExpectations(result: PNGetMessageActionsResult) { + assertEquals(3, result.actions.size) + assertEquals(2, result.actions.filter { it.uuid == "abc" }.size) + assertEquals(2, result.actions.filter { it.value == "\uD83D\uDE00" }.size) + assertEquals(2, result.actions.filter { it.messageTimetoken == 100L }.size) + } + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/ch1")) + .withQueryParam("start", equalTo("991")) + .withQueryParam("end", equalTo("969")) + .withQueryParam("limit", equalTo("5")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/message_actions/GetMessageActionsTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/message_actions/GetMessageActionsTestSuite.kt new file mode 100644 index 000000000..e68c585d9 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/message_actions/GetMessageActionsTestSuite.kt @@ -0,0 +1,65 @@ +package com.pubnub.api.suite.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.message_actions.GetMessageActions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals + +class GetMessageActionsTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_msga" + + override fun pnOperation() = PNOperationType.PNGetMessageActions + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): GetMessageActions { + return pubnub.getMessageActions().apply { + channel = "ch1" + } + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "data": [ + { + "messageTimetoken": "100", + "type": "reaction", + "uuid": "abc", + "value": "smiley", + "actionTimetoken": "200" + } + ] + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"status":200,"data":null}""", + """{"status":200}""" + ) + + override fun verifyResultExpectations(result: PNGetMessageActionsResult) { + assertEquals(1, result.actions.size) + result.actions[0].apply { + assertEquals(100L, messageTimetoken) + assertEquals("reaction", type) + assertEquals("abc", uuid) + assertEquals("smiley", value) + assertEquals(200L, actionTimetoken) + + } + } + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/ch1")) + .withQueryParam("start", absent()) + .withQueryParam("end", absent()) + .withQueryParam("limit", absent()) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/message_actions/RemoveMessageActionsTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/message_actions/RemoveMessageActionsTestSuite.kt new file mode 100644 index 000000000..40d405f75 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/message_actions/RemoveMessageActionsTestSuite.kt @@ -0,0 +1,47 @@ +package com.pubnub.api.suite.message_actions + +import com.github.tomakehurst.wiremock.client.WireMock.delete +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.endpoints.message_actions.RemoveMessageAction +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveMessageActionsTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_msga" + + override fun pnOperation() = PNOperationType.PNDeleteMessageAction + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveMessageAction { + return pubnub.removeMessageAction().apply { + channel = "ch1" + messageTimetoken = 100 + actionTimetoken = 200 + } + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "data": {} + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun verifyResultExpectations(result: PNRemoveMessageActionResult) { + + } + + override fun mappingBuilder() = + delete(urlPathEqualTo("/v1/message-actions/mySubscribeKey/channel/ch1/message/100/action/200")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file From e9d0f08aaac7c99f44547697ae5a523c0e748a95 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:35:35 +0200 Subject: [PATCH 87/98] Implement test suite for Presence APIs --- .../api/suite/presence/GetStateTestSuite.kt | 76 +++++++++++++++++++ .../api/suite/presence/HeartbeatTestSuite.kt | 49 ++++++++++++ .../api/suite/presence/HereNowTestSuite.kt | 60 +++++++++++++++ .../api/suite/presence/LeaveTestSuite.kt | 48 ++++++++++++ .../api/suite/presence/SetStateTestSuite.kt | 73 ++++++++++++++++++ .../api/suite/presence/WhereNowTestSuite.kt | 56 ++++++++++++++ 6 files changed, 362 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/GetStateTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/HeartbeatTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/HereNowTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/LeaveTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/SetStateTestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/presence/WhereNowTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/GetStateTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/GetStateTestSuite.kt new file mode 100644 index 000000000..7a92ba489 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/GetStateTestSuite.kt @@ -0,0 +1,76 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.pubnub.api.endpoints.presence.GetState +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNGetStateResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.OptionalScenario +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse + +class GetStateTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNGetState + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): GetState { + return pubnub.getPresenceState().apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: PNGetStateResult) { + assertEquals(1, result.stateByUUID.keys.size) + assertEquals(JsonObject().apply { addProperty("text", "hello") }, result.stateByUUID["ch1"]) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "payload": { + "text": "hello" + }, + "uuid": "myUUID", + "channel": "ch1", + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder = + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("""{"payload":{}}""") } + additionalChecks = { pnStatus, result -> + assertFalse(pnStatus.error) + assertEquals(JsonObject(), result!!.stateByUUID["ch1"]) + } + }, + OptionalScenario().apply { + responseBuilder = { withBody("""{"payload":null}""") } + additionalChecks = { pnStatus, result -> + assertFalse(pnStatus.error) + assertEquals(JsonNull.INSTANCE, result!!.stateByUUID["ch1"]) + } + } + ) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/HeartbeatTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/HeartbeatTestSuite.kt new file mode 100644 index 000000000..5fe6760e5 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/HeartbeatTestSuite.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.presence.Heartbeat +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertTrue + +class HeartbeatTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNHeartbeatOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): Heartbeat { + return Heartbeat( + pubnub = pubnub, + channels = listOf("ch1") + ) + } + + override fun verifyResultExpectations(result: Boolean) { + assertTrue(result) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder = + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .withQueryParam("heartbeat", equalTo(pubnub.configuration.presenceTimeout.toString())) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun voidResponse() = true + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/HereNowTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/HereNowTestSuite.kt new file mode 100644 index 000000000..50a38a4c4 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/HereNowTestSuite.kt @@ -0,0 +1,60 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.presence.HereNow +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNHereNowResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals + +class HereNowTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNHereNowOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): HereNow { + return pubnub.hereNow().apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: PNHereNowResult) { + assertEquals(1, result.totalChannels) + assertEquals(1, result.totalOccupancy) + assertEquals(1, result.channels.size) + assertEquals("user_1", result.channels["ch1"]!!.occupants[0].uuid) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "occupancy": 1, + "uuids": [ + "user_1" + ], + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"occupancy": 0, "uuids": null}""", + """{"payload": {"channels": null, "total_channels": 0, "total_occupancy": 0}}""", + """{"payload": {}}""", + """{"payload": null}""" + ) + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1")) + .withQueryParam("state", absent()) + .withQueryParam("disable_uuids", absent()) + .withQueryParam("channel-group", absent()) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/LeaveTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/LeaveTestSuite.kt new file mode 100644 index 000000000..508b011aa --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/LeaveTestSuite.kt @@ -0,0 +1,48 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.endpoints.presence.Leave +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertTrue + +class LeaveTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNUnsubscribeOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): Leave { + return Leave(pubnub).apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: Boolean) { + assertTrue(result) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder(): MappingBuilder = + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/leave")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun voidResponse() = true + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/SetStateTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/SetStateTestSuite.kt new file mode 100644 index 000000000..a975aa51c --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/SetStateTestSuite.kt @@ -0,0 +1,73 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.google.gson.JsonObject +import com.pubnub.api.endpoints.presence.SetState +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.presence.PNSetStateResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.* + +class SetStateTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNSetStateOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): SetState { + return pubnub.setPresenceState().apply { + channels = listOf("ch1") + state = mapOf("text" to "hello") + } + } + + override fun verifyResultExpectations(result: PNSetStateResult) { + assertEquals(JsonObject().apply { addProperty("text", "hello") }, result.state) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "payload": { + "text": "hello" + }, + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"payload":null}""" + ) + + override fun mappingBuilder(): MappingBuilder = + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .withQueryParam("state", equalTo("""{"text":"hello"}""")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + result = Result.SUCCESS + responseBuilder = { withBody("""{"payload":{}}""") } + additionalChecks = { pnStatus: PNStatus, result: PNSetStateResult? -> + assertFalse(pnStatus.error) + assertTrue(result!!.state.asJsonObject.keySet().isEmpty()) + } + }, + OptionalScenario().apply { + result = Result.FAIL + responseBuilder = { withBody("""{"payload":null}""") } + additionalChecks = { pnStatus: PNStatus, result: PNSetStateResult? -> + assertTrue(pnStatus.error) + assertNull(result) + } + } + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/presence/WhereNowTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/presence/WhereNowTestSuite.kt new file mode 100644 index 000000000..f75c35a8b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/presence/WhereNowTestSuite.kt @@ -0,0 +1,56 @@ +package com.pubnub.api.suite.presence + +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.pubnub.api.endpoints.presence.WhereNow +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.presence.PNWhereNowResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals + +class WhereNowTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_pres" + + override fun pnOperation() = PNOperationType.PNWhereNowOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): WhereNow { + return pubnub.whereNow().apply { + + } + } + + override fun verifyResultExpectations(result: PNWhereNowResult) { + assertEquals(1, result.channels.size) + assertEquals("ch1", result.channels[0]) + } + + override fun successfulResponseBody() = """ + { + "status": 200, + "message": "OK", + "payload": { + "channels": [ + "ch1" + ] + }, + "service": "Presence" + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = listOf( + """{"payload":{"channels":null}}""", + """{"payload": {}}""", + """{"payload": null}""" + ) + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + +} \ No newline at end of file From e4d5796af2808f372e239adc4664e25213e91302 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:35:49 +0200 Subject: [PATCH 88/98] Implement test suite for PubSub APIs --- .../suite/{ => pubsub}/PublishGetTestSuite.kt | 18 +++--- .../{ => pubsub}/PublishPostTestSuite.kt | 12 ++-- .../api/suite/{ => pubsub}/SignalTestSuite.kt | 16 ++++-- .../api/suite/pubsub/SubscribeTestSuite.kt | 56 +++++++++++++++++++ 4 files changed, 85 insertions(+), 17 deletions(-) rename src/test/kotlin/com/pubnub/api/suite/{ => pubsub}/PublishGetTestSuite.kt (76%) rename src/test/kotlin/com/pubnub/api/suite/{ => pubsub}/PublishPostTestSuite.kt (86%) rename src/test/kotlin/com/pubnub/api/suite/{ => pubsub}/SignalTestSuite.kt (75%) create mode 100644 src/test/kotlin/com/pubnub/api/suite/pubsub/SubscribeTestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/pubsub/PublishGetTestSuite.kt similarity index 76% rename from src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/pubsub/PublishGetTestSuite.kt index 91a7344ce..56c8e2a62 100644 --- a/src/test/kotlin/com/pubnub/api/suite/PublishGetTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/pubsub/PublishGetTestSuite.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.suite +package com.pubnub.api.suite.pubsub import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.get @@ -7,6 +7,10 @@ import com.google.gson.Gson import com.pubnub.api.endpoints.pubsub.Publish import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.PUB +import com.pubnub.api.suite.SUB import org.junit.jupiter.api.Assertions.assertEquals import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -21,8 +25,8 @@ class PublishGetTestSuite : EndpointTestSuite() { override fun snippet(): Publish { return pubnub.publish().apply { - channel = "foo" - message = "bar" + channel = "ch1" + message = "ch2" } } @@ -37,20 +41,20 @@ class PublishGetTestSuite : EndpointTestSuite() { override fun mappingBuilder(): MappingBuilder { return get( urlPathEqualTo( - "/publish/myPublishKey/mySubscribeKey/0/foo/0/%s".format( - URLEncoder.encode(Gson().toJson("bar"), StandardCharsets.UTF_8.name()) + "/publish/myPublishKey/mySubscribeKey/0/ch1/0/%s".format( + URLEncoder.encode(Gson().toJson("ch2"), StandardCharsets.UTF_8.name()) ) ) )!! } - override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() /*companion object { @JvmStatic fun predefined(): Stream? { return Stream.of( - Arguments.of("foo"), + Arguments.of("ch1"), Arguments.of(123), Arguments.of(3.14) ) diff --git a/src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/pubsub/PublishPostTestSuite.kt similarity index 86% rename from src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/pubsub/PublishPostTestSuite.kt index 60dd7f4f4..345973286 100644 --- a/src/test/kotlin/com/pubnub/api/suite/PublishPostTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/pubsub/PublishPostTestSuite.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.suite +package com.pubnub.api.suite.pubsub import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.* @@ -6,6 +6,10 @@ import com.google.gson.Gson import com.pubnub.api.endpoints.pubsub.Publish import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.PUB +import com.pubnub.api.suite.SUB import org.json.JSONObject import org.junit.jupiter.api.Assertions.assertEquals @@ -19,7 +23,7 @@ class PublishPostTestSuite : EndpointTestSuite() { override fun snippet(): Publish { return pubnub.publish().apply { - channel = "foo" + channel = "ch1" message = mapOf( "name" to "john", "age" to 30, @@ -42,7 +46,7 @@ class PublishPostTestSuite : EndpointTestSuite() { override fun mappingBuilder(): MappingBuilder { return post( - urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/foo/0") + urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/ch1/0") ) .withQueryParam( "meta", equalToJson("""{"city":"sf"}""") @@ -60,6 +64,6 @@ class PublishPostTestSuite : EndpointTestSuite() { )!! } - override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/pubsub/SignalTestSuite.kt similarity index 75% rename from src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt rename to src/test/kotlin/com/pubnub/api/suite/pubsub/SignalTestSuite.kt index 98500122f..a751791d7 100644 --- a/src/test/kotlin/com/pubnub/api/suite/SignalTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/pubsub/SignalTestSuite.kt @@ -1,4 +1,4 @@ -package com.pubnub.api.suite +package com.pubnub.api.suite.pubsub import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.get @@ -7,6 +7,10 @@ import com.google.gson.Gson import com.pubnub.api.endpoints.pubsub.Signal import com.pubnub.api.enums.PNOperationType import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.PUB +import com.pubnub.api.suite.SUB import org.junit.jupiter.api.Assertions.assertEquals import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -21,8 +25,8 @@ class SignalTestSuite : EndpointTestSuite() { override fun snippet(): Signal { return pubnub.signal().apply { - channel = "foo" - message = "bar" + channel = "ch1" + message = "ch2" } } @@ -37,13 +41,13 @@ class SignalTestSuite : EndpointTestSuite() { override fun mappingBuilder(): MappingBuilder { return get( urlPathEqualTo( - "/signal/myPublishKey/mySubscribeKey/0/foo/0/%s".format( - URLEncoder.encode(Gson().toJson("bar"), StandardCharsets.UTF_8.name()) + "/signal/myPublishKey/mySubscribeKey/0/ch1/0/%s".format( + URLEncoder.encode(Gson().toJson("ch2"), StandardCharsets.UTF_8.name()) ) ) )!! } - override fun affectedChannelsAndGroups() = listOf("foo") to emptyList() + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/pubsub/SubscribeTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/pubsub/SubscribeTestSuite.kt new file mode 100644 index 000000000..062496e52 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/pubsub/SubscribeTestSuite.kt @@ -0,0 +1,56 @@ +package com.pubnub.api.suite.pubsub + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.pubsub.Subscribe +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.server.SubscribeEnvelope +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue + +class SubscribeTestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "" + + override fun pnOperation() = PNOperationType.PNSubscribeOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): Subscribe { + return Subscribe(pubnub).apply { + channels = listOf("ch1") + } + } + + override fun verifyResultExpectations(result: SubscribeEnvelope) { + assertEquals(100, result.metadata.timetoken) + assertEquals("1", result.metadata.region) + assertTrue(result.messages.isEmpty()) + } + + override fun successfulResponseBody() = """ + { + "t": { + "t": "100", + "r": 1 + }, + "m": [] + } + """.trimIndent() + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1/0")) + .withQueryParam("tt", absent()) + .withQueryParam("tr", absent()) + .withQueryParam("filter-expr", absent()) + .withQueryParam("state", absent()) + .withQueryParam("channel-group", absent()) + .withQueryParam("heartbeat", equalTo("300")) + + override fun affectedChannelsAndGroups() = listOf("ch1") to emptyList() + +} \ No newline at end of file From 42f7f06e5a96e51b2c046cf5bbd04b285a0e4d94 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:36:02 +0200 Subject: [PATCH 89/98] Implement test suite for MobilePush APIs --- .../push/add/AddChannelsToPushV1TestSuite.kt | 68 +++++++++++++++++++ .../push/add/AddChannelsToPushV2TestSuite.kt | 49 +++++++++++++ .../list/ListPushProvisionsV1TestSuite.kt | 63 +++++++++++++++++ .../list/ListPushProvisionsV2TestSuite.kt | 64 +++++++++++++++++ .../remove/RemoveAllFromPushV1TestSuite.kt | 46 +++++++++++++ .../remove/RemoveAllFromPushV2TestSuite.kt | 48 +++++++++++++ .../RemoveChannelsFromPushV1TestSuite.kt | 48 +++++++++++++ .../RemoveChannelsFromPushV2TestSuite.kt | 49 +++++++++++++ 8 files changed, 435 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV1TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV2TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV1TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV2TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV1TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV2TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV1TestSuite.kt create mode 100644 src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV2TestSuite.kt diff --git a/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV1TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV1TestSuite.kt new file mode 100644 index 000000000..097084750 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV1TestSuite.kt @@ -0,0 +1,68 @@ +package com.pubnub.api.suite.push.add + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.PubNubError +import com.pubnub.api.endpoints.push.AddChannelsToPush +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.push.PNPushAddChannelResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue + +class AddChannelsToPushV1TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNAddPushNotificationsOnChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): AddChannelsToPush { + return pubnub.addPushNotificationsOnChannels().apply { + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2") + deviceId = "12345" + } + } + + override fun verifyResultExpectations(result: PNPushAddChannelResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Modified Channels"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/12345")) + .withQueryParam("type", equalTo("gcm")) + .withQueryParam("add", equalTo("ch1,ch2")) + .withQueryParam("environment", absent()) + .withQueryParam("topic", absent()) + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to emptyList() + + override fun voidResponse() = true + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + val body = "{\"error\":\"Use of the mobile push notifications API requires Push Notifications" + + " which is not enabled for this subscribe key. Login to your PubNub Dashboard Account" + + " and enable Push Notifications. " + + "Contact support@pubnub.com if you require further assistance.\"}" + result = Result.FAIL + responseBuilder = { withBody(body).withStatus(400) } + pnError = PubNubError.HTTP_ERROR + additionalChecks = { status: PNStatus, _: PNPushAddChannelResult? -> + assertTrue(voidResponse()) + assertEquals(body, status.exception!!.jso) + } + } + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV2TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV2TestSuite.kt new file mode 100644 index 000000000..fd8c60a90 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/add/AddChannelsToPushV2TestSuite.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.suite.push.add + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.AddChannelsToPush +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushAddChannelResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class AddChannelsToPushV2TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNAddPushNotificationsOnChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): AddChannelsToPush { + return pubnub.addPushNotificationsOnChannels().apply { + pushType = PNPushType.APNS2 + channels = listOf("ch1", "ch2") + deviceId = "12345" + topic = "news" + } + } + + override fun verifyResultExpectations(result: PNPushAddChannelResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Modified Channels"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/12345")) + .withQueryParam("type", absent()) + .withQueryParam("add", equalTo("ch1,ch2")) + .withQueryParam("environment", equalTo("development")) + .withQueryParam("topic", equalTo("news")) + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV1TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV1TestSuite.kt new file mode 100644 index 000000000..1fa224c27 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV1TestSuite.kt @@ -0,0 +1,63 @@ +package com.pubnub.api.suite.push.list + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.ListPushProvisions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse + +class ListPushProvisionsV1TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNPushNotificationEnabledChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): ListPushProvisions { + return pubnub.auditPushChannelProvisions().apply { + pushType = PNPushType.FCM + deviceId = "12345" + } + } + + override fun verifyResultExpectations(result: PNPushListProvisionsResult) { + assertEquals(2, result.channels.size) + assertEquals("ch1", result.channels[0]) + assertEquals("ch2", result.channels[1]) + } + + override fun successfulResponseBody(): String { + return """["ch1", "ch2"]""" + } + + override fun unsuccessfulResponseBodyList() = listOf( + """["ch1","ch2",{}]""" + ) + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/12345")) + .withQueryParam("type", equalTo("gcm")) + .withQueryParam("environment", absent()) + .withQueryParam("topic", absent()) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("[]") } + result = Result.SUCCESS + additionalChecks = { status: PNStatus, result: PNPushListProvisionsResult? -> + assertFalse(status.error) + assertEquals(0, result!!.channels.size) + } + } + ) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV2TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV2TestSuite.kt new file mode 100644 index 000000000..b37b99ee4 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/list/ListPushProvisionsV2TestSuite.kt @@ -0,0 +1,64 @@ +package com.pubnub.api.suite.push.list + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.ListPushProvisions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse + +class ListPushProvisionsV2TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNPushNotificationEnabledChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): ListPushProvisions { + return pubnub.auditPushChannelProvisions().apply { + pushType = PNPushType.APNS2 + deviceId = "12345" + topic = "news" + } + } + + override fun verifyResultExpectations(result: PNPushListProvisionsResult) { + assertEquals(2, result.channels.size) + assertEquals("ch1", result.channels[0]) + assertEquals("ch2", result.channels[1]) + } + + override fun successfulResponseBody(): String { + return """["ch1", "ch2"]""" + } + + override fun unsuccessfulResponseBodyList() = listOf( + """["ch1","ch2",{}]""" + ) + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/12345")) + .withQueryParam("type", absent()) + .withQueryParam("environment", equalTo("development")) + .withQueryParam("topic", equalTo("news")) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("[]") } + result = Result.SUCCESS + additionalChecks = { status: PNStatus, result: PNPushListProvisionsResult? -> + assertFalse(status.error) + assertEquals(0, result!!.channels.size) + } + } + ) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV1TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV1TestSuite.kt new file mode 100644 index 000000000..0ae27719a --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV1TestSuite.kt @@ -0,0 +1,46 @@ +package com.pubnub.api.suite.push.remove + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.RemoveAllPushChannelsForDevice +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveAllChannelsResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveAllFromPushV1TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNRemoveAllPushNotificationsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveAllPushChannelsForDevice { + return pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + pushType = PNPushType.FCM + deviceId = "12345" + } + } + + override fun verifyResultExpectations(result: PNPushRemoveAllChannelsResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Removed Device"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/12345/remove")) + .withQueryParam("type", equalTo("gcm")) + .withQueryParam("environment", absent()) + .withQueryParam("topic", absent()) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV2TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV2TestSuite.kt new file mode 100644 index 000000000..6f2d0a750 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveAllFromPushV2TestSuite.kt @@ -0,0 +1,48 @@ +package com.pubnub.api.suite.push.remove + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.RemoveAllPushChannelsForDevice +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveAllChannelsResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveAllFromPushV2TestSuite : + EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNRemoveAllPushNotificationsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveAllPushChannelsForDevice { + return pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + pushType = PNPushType.APNS2 + deviceId = "12345" + topic = "news" + } + } + + override fun verifyResultExpectations(result: PNPushRemoveAllChannelsResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Removed Device"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/12345/remove")) + .withQueryParam("type", absent()) + .withQueryParam("environment", equalTo("development")) + .withQueryParam("topic", equalTo("news")) + + override fun affectedChannelsAndGroups() = emptyList() to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV1TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV1TestSuite.kt new file mode 100644 index 000000000..c7a702e06 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV1TestSuite.kt @@ -0,0 +1,48 @@ +package com.pubnub.api.suite.push.remove + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.RemoveChannelsFromPush +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveChannelsFromPushV1TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNRemovePushNotificationsFromChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveChannelsFromPush { + return pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.FCM + channels = listOf("ch1", "ch2") + deviceId = "12345" + } + } + + override fun verifyResultExpectations(result: PNPushRemoveChannelResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Modified Channels"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/12345")) + .withQueryParam("type", equalTo("gcm")) + .withQueryParam("remove", equalTo("ch1,ch2")) + .withQueryParam("environment", absent()) + .withQueryParam("topic", absent()) + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV2TestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV2TestSuite.kt new file mode 100644 index 000000000..2ff411155 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/suite/push/remove/RemoveChannelsFromPushV2TestSuite.kt @@ -0,0 +1,49 @@ +package com.pubnub.api.suite.push.remove + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.pubnub.api.endpoints.push.RemoveChannelsFromPush +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNPushType +import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult +import com.pubnub.api.suite.AUTH +import com.pubnub.api.suite.EndpointTestSuite +import com.pubnub.api.suite.SUB + +class RemoveChannelsFromPushV2TestSuite : EndpointTestSuite() { + + override fun telemetryParamName() = "l_push" + + override fun pnOperation() = PNOperationType.PNRemovePushNotificationsFromChannelsOperation + + override fun requiredKeys() = SUB + AUTH + + override fun snippet(): RemoveChannelsFromPush { + return pubnub.removePushNotificationsFromChannels().apply { + pushType = PNPushType.APNS2 + channels = listOf("ch1", "ch2") + deviceId = "12345" + topic = "news" + } + } + + override fun verifyResultExpectations(result: PNPushRemoveChannelResult) { + + } + + override fun successfulResponseBody(): String { + return """[1, "Modified Channels"]""" + } + + override fun unsuccessfulResponseBodyList() = emptyList() + + override fun mappingBuilder() = + get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/12345")) + .withQueryParam("type", absent()) + .withQueryParam("remove", equalTo("ch1,ch2")) + .withQueryParam("environment", equalTo("development")) + .withQueryParam("topic", equalTo("news")) + + override fun affectedChannelsAndGroups() = listOf("ch1", "ch2") to emptyList() + + override fun voidResponse() = true +} \ No newline at end of file From a91efa9df5ca6959b48d227a1963db6fbc38f6cb Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:37:45 +0200 Subject: [PATCH 90/98] Implement test suite for Time APIs --- .../com/pubnub/api/suite/TimeTestSuite.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt index 1ee07747d..cef66e7c8 100644 --- a/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/TimeTestSuite.kt @@ -1,12 +1,12 @@ package com.pubnub.api.suite import com.github.tomakehurst.wiremock.client.WireMock.get -import com.github.tomakehurst.wiremock.client.WireMock.urlMatching +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo import com.pubnub.api.endpoints.Time import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.PNTimeResult -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.* class TimeTestSuite : EndpointTestSuite() { override fun telemetryParamName() = "l_time" @@ -16,12 +16,7 @@ class TimeTestSuite : EndpointTestSuite() { override fun requiredKeys() = 0 override fun snippet(): Time { - return pubnub.time().apply { - queryParam = mapOf( - "key_1_space" to "ab cd", - "key_2_plus" to "ab+cd" - ) - } + return pubnub.time() } override fun verifyResultExpectations(result: PNTimeResult) { @@ -35,22 +30,28 @@ class TimeTestSuite : EndpointTestSuite() { """[false]""" ) - override fun optionalScenarioList(): List { + override fun optionalScenarioList(): List> { return listOf( - OptionalScenario().apply { + OptionalScenario().apply { responseBuilder = { withBody("[wrong]") } result = Result.FAIL - additionalChecks = { status -> + additionalChecks = { status: PNStatus, result: PNTimeResult? -> assertTrue(status.error) + assertNull(result) } }, - OptionalScenario().apply { + OptionalScenario().apply { responseBuilder = { withBody("[123]") } + additionalChecks = { status: PNStatus, result: PNTimeResult? -> + assertFalse(status.error) + assertEquals(123, result!!.timetoken) + } } ) } - override fun mappingBuilder() = get(urlMatching("/time/0.*")) + override fun mappingBuilder() = get(urlPathEqualTo("/time/0")) override fun affectedChannelsAndGroups() = emptyList() to emptyList() + } \ No newline at end of file From a463229b55b8d24722dfaf6ed1911b3aac02340b Mon Sep 17 00:00:00 2001 From: azurqsd Date: Fri, 5 Jun 2020 15:37:59 +0200 Subject: [PATCH 91/98] Implement test suite --- .../com/pubnub/api/suite/EndpointTestSuite.kt | 152 +++++++++++++----- 1 file changed, 114 insertions(+), 38 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt index 679100f6c..444930640 100644 --- a/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt @@ -9,11 +9,15 @@ import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.PNStatus +import org.awaitility.Awaitility +import org.awaitility.Durations import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean -typealias MoreChecks = (status: PNStatus) -> Unit +typealias AsyncCheck = (pnStatus: PNStatus, result: T?) -> Unit abstract class EndpointTestSuite, R> : BaseTest() { @@ -29,41 +33,66 @@ abstract class EndpointTestSuite, R> : BaseTest() { abstract fun mappingBuilder(): MappingBuilder abstract fun affectedChannelsAndGroups(): Pair, List> - open fun optionalScenarioList(): List = emptyList() + open fun optionalScenarioList(): List> = emptyList() + open fun voidResponse() = false override fun onBefore() { super.onBefore() expectedStub = stubFor(mappingBuilder().willReturn(aResponse().withBody(successfulResponseBody()))) + pubnub.configuration.includeInstanceIdentifier = false + pubnub.configuration.includeRequestIdentifier = false } @Test fun testTelemetryParameter() { - val success = AtomicBoolean() + if (pnOperation() == PNOperationType.PNSubscribeOperation) + return stubTimeEndpoint() + wireMockServer.removeStub(expectedStub) + + stubFor( + mappingBuilder() + .willReturn( + aResponse() + .withFixedDelay(50) + .withBody(successfulResponseBody()) + ) + ) + lateinit var telemetryParamName: String snippet().eval { _, status -> assertFalse(status.error) assertEquals(pnOperation(), status.operation) assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) - telemetryParamName = "l_${status.operation.queryParam}" + telemetryParamName = "l_${status.operation.queryParam!!}" assertEquals(telemetryParamName(), telemetryParamName) } - pubnub.time().async { _, status -> - assertFalse(status.error) - assertNotNull(status.param(telemetryParamName)) - success.set(true) - } + Awaitility.await() + .pollInterval(Durations.FIVE_HUNDRED_MILLISECONDS) + .pollDelay(Durations.FIVE_HUNDRED_MILLISECONDS) + .atMost(Durations.FIVE_SECONDS) + .until { + val latch = CountDownLatch(1) - success.listen() + pubnub.time().async { _, status -> + assertFalse(status.error) + assertNotNull(status.param(telemetryParamName)) + latch.countDown() + } + + latch.await(500, TimeUnit.MILLISECONDS) + } } @Test fun testSuccessAsync() { snippet().eval { result, status -> + // todo + // status.exception?.printStackTrace() assertFalse(status.error) assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) assertEquals(pnOperation(), status.operation) @@ -84,10 +113,11 @@ abstract class EndpointTestSuite, R> : BaseTest() { testPublishKey() testAuthKeySync() testAuthKeyAsync() + testSecretKey() } @Test - fun testWrongResponsesSync() { + fun testUnsuccessfulResponsesSync() { wireMockServer.removeStub(expectedStub) unsuccessfulResponseBodyList().forEach { @@ -96,14 +126,15 @@ abstract class EndpointTestSuite, R> : BaseTest() { testSuccessSync() failTest() } catch (e: Exception) { - // assertPnException(PubNubError.PARSING_ERROR, e) + e.printStackTrace() + assertPnException(PubNubError.PARSING_ERROR, e) } wireMockServer.removeStub(stub) } } @Test - fun testWrongResponsesAsync() { + fun testUnsuccessfulResponsesAsync() { wireMockServer.removeStub(expectedStub) unsuccessfulResponseBodyList().forEach { @@ -117,19 +148,48 @@ abstract class EndpointTestSuite, R> : BaseTest() { } @Test - fun testUsualWrongResponsesSync() { + fun testUsualWrongResponses() { wireMockServer.removeStub(expectedStub) - val list = listOf(emptyJson(), noContent(), aResponse().withBody("{}")) + val map = hashMapOf( + "empty_json" to emptyJson(), + "no_content" to noContent(), + "malformed" to aResponse().withBody("{") + ) + + if (pnOperation() == PNOperationType.PNSubscribeOperation) + map.remove("empty_json") - list.forEach { - val stub = stubFor(mappingBuilder().willReturn(it)) + map.forEach { + val stub = stubFor(mappingBuilder().willReturn(it.value)) try { - testSuccessSync() - failTest() + val result = snippet().sync() + if (voidResponse()) { + assertNotNull(result) + } else { + failTest(it.key) + } } catch (e: Exception) { + if (voidResponse()) { + failTest(it.key) + } assertPnException(PubNubError.PARSING_ERROR, e) } + + snippet().eval { result, status -> + assertEquals(!voidResponse(), status.error) + + if (!voidResponse()) { + assertNull(result) + assertPnException(PubNubError.PARSING_ERROR, status) + assertEquals(PNStatusCategory.PNMalformedResponseCategory, status.category) + } else { + assertNotNull(result) + assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) + } + + } + wireMockServer.removeStub(stub) } } @@ -166,7 +226,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { val stub = stubFor(mappingBuilder().willReturn(it.build())) snippet().eval { result, status -> - it.additionalChecks.invoke(status) + it.additionalChecks.invoke(status, result) if (it.result == Result.SUCCESS) { assertFalse(status.error) result!! @@ -259,6 +319,21 @@ abstract class EndpointTestSuite, R> : BaseTest() { } } + private fun testSecretKey() { + pubnub.configuration.secretKey = " " + try { + testSuccessSync() + if (requiredKeys().contains(SEC)) { + failTest() + } + } catch (e: Exception) { + if (requiredKeys().contains(SEC)) { + assertPnException(PubNubError.SECRET_KEY_MISSING, e) + } + } + pubnub.configuration.secretKey = "mySecretKey" + } + private fun runSync() { val result = snippet().sync()!! verifyResultExpectations(result) @@ -267,7 +342,10 @@ abstract class EndpointTestSuite, R> : BaseTest() { private fun stubTimeEndpoint() { stubFor( get(urlMatching("/time/0.*")) - .willReturn(aResponse().withBody("[1000]")) + .willReturn( + aResponse() + .withBody("[1000]") + ) ) } } @@ -276,46 +354,44 @@ private fun extractKeys(n: Int): List { val keys = mutableListOf() var n = n while (n > 0) { - val power = highestPowerOf2(n); + val power = { + var res = 0 + for (i in n downTo 1) { + if (i and i - 1 == 0) { + res = i + break + } + } + res + }.invoke() keys.add(power) n -= power } return keys } -private fun highestPowerOf2(n: Int): Int { - var res = 0 - for (i in n downTo 1) { - // If i is a power of 2 - if (i and i - 1 == 0) { - res = i - break - } - } - return res -} - val SUB = 0b001 val PUB = 0b010 val AUTH = 0b100 +val SEC = 0b1000 private fun Int.contains(sub: Int): Boolean { return extractKeys(this).contains(sub) } -private fun Endpoint.eval(pieceOfCode: (result: Output?, status: PNStatus) -> Unit) { +private fun Endpoint.eval(function: (result: Output?, status: PNStatus) -> Unit) { val success = AtomicBoolean() async { result, status -> - pieceOfCode.invoke(result, status) + function.invoke(result, status) success.set(true) } success.listen() } -class OptionalScenario { +class OptionalScenario { var responseBuilder: ResponseDefinitionBuilder.() -> ResponseDefinitionBuilder = { this } - var additionalChecks: MoreChecks = {} + var additionalChecks: AsyncCheck = { status: PNStatus, result: R? -> } var result: Result = Result.SUCCESS var pnError: PubNubError? = null From d7477da028a4cdde0c66cf2739dacdfbb6ec42e1 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:27:45 +0200 Subject: [PATCH 92/98] Cleanup --- build.gradle | 91 +++++++++++++------ settings.gradle | 2 +- src/main/kotlin/com/pubnub/api/Endpoint.kt | 17 ---- .../kotlin/com/pubnub/api/PNConfiguration.kt | 5 +- src/main/kotlin/com/pubnub/api/PubNub.kt | 7 +- src/main/kotlin/com/pubnub/api/PubNubUtil.kt | 13 +-- .../com/pubnub/api/builder/PubSubBuilders.kt | 6 +- .../pubnub/api/callbacks/SubscribeCallback.kt | 9 -- .../pubnub/api/managers/BasePathManager.kt | 3 +- .../pubnub/api/managers/DuplicationManager.kt | 5 +- .../pubnub/api/managers/ListenerManager.kt | 2 +- .../api/managers/ReconnectionManager.kt | 14 +-- .../pubnub/api/managers/RetrofitManager.kt | 2 +- .../com/pubnub/api/managers/StateManager.kt | 2 +- .../api/managers/SubscriptionManager.kt | 8 +- .../pubnub/api/models/consumer/PNErrorData.kt | 12 --- .../api/models/consumer/PNPublishResult.kt | 2 +- .../api/models/consumer/PNSignalResult.kt | 10 -- .../api/models/consumer/PNTimeResult.kt | 2 +- .../PNAccessManagerGrantResults.kt | 2 +- .../models/consumer/history/PNFetchMessage.kt | 6 +- .../consumer/history/PNHistoryResult.kt | 2 +- .../models/consumer/presence/PNWhereNow.kt | 2 +- .../consumer/pubsub/BasePubSubResult.kt | 15 +-- .../models/consumer/pubsub/PNMessageResult.kt | 6 +- .../consumer/pubsub/PNPresenceEventResult.kt | 28 +++--- .../message_actions/PNMessageActionResult.kt | 2 +- .../pubsub/objects/PNMembershipResult.kt | 14 --- .../consumer/pubsub/objects/PNSpaceResult.kt | 14 --- .../consumer/pubsub/objects/PNUserResult.kt | 14 --- .../api/workers/SubscribeMessageWorker.kt | 10 +- 31 files changed, 140 insertions(+), 187 deletions(-) delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt delete mode 100644 src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt diff --git a/build.gradle b/build.gradle index c759bc1ab..3f5318a3a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,21 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '1.3.50' - id 'java-library' + id 'org.jetbrains.kotlin.jvm' version '1.3.72' id 'maven' - id 'io.gitlab.arturbosch.detekt' version '1.1.1' + id 'com.github.johnrengelman.shadow' version '4.0.2' + id 'java-library' + id 'com.bmuschko.nexus' version '2.3.1' } -group 'com.pubnub' -version '0.0.1' +group = 'com.pubnub' +version = '4.0.0' repositories { mavenCentral() jcenter() } -configurations { - ktlint -} - -ext { - junitVersion = '5.6.2' -} - dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' implementation("com.squareup.retrofit2:retrofit:2.6.2") @@ -31,24 +24,22 @@ dependencies { api 'com.google.code.gson:gson:2.8.6' implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.6.2' - // implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28' - // implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.28' - // implementation group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.28' - // implementation group: 'log4j', name: 'log4j', version: '1.2.17' + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28' + testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.28' testImplementation 'org.awaitility:awaitility-kotlin:4.0.1' - - implementation 'com.github.tomakehurst:wiremock:2.26.3' + testImplementation 'com.github.tomakehurst:wiremock:2.26.3' implementation group: 'org.json', name: 'json', version: '20190722' - // testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" - // testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" - // testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" + testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' +} + +shadowJar { + classifier = "all" } -test { +tasks.withType(Test) { useJUnitPlatform { includeEngines 'junit-jupiter' } @@ -59,4 +50,52 @@ compileKotlin { } compileTestKotlin { kotlinOptions.jvmTarget = "1.8" -} \ No newline at end of file +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +extraArchive { + sources = true + tests = true + javadoc = true +} + +nexus { + sign = true + repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' + snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots' +} + +modifyPom { + project { + name 'PubNub Kotlin SDK' + description 'PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of\n' + + ' broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter\n' + + ' second!' + url 'https://github.com/pubnub/kotlin' + inceptionYear '2009' + + scm { + url 'https://github.com/pubnub/kotlin' + } + + licenses { + license { + name 'MIT License' + url 'https://github.com/pubnub/pubnub-api/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'PubNub' + name 'PubNub' + email 'support@pubnub.com' + } + } + } +} + +build.finalizedBy(shadowJar) \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index dd638c75a..29161764f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'pubnub-kt' \ No newline at end of file +rootProject.name = 'pubnub-kotlin' \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index 7918f791a..076c98219 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -188,23 +188,6 @@ abstract class Endpoint(protected val pubnub: PubNub) { ) } - /*protected fun encodeParams(params: Map): Map { - val encodedParams = HashMap(params) - if (encodedParams.containsKey("auth")) { - encodedParams["auth"] = encodedParams["auth"]?.let { - PubNubUtil.urlEncode(it) - } - } - return encodedParams - }*/ - - protected fun appendInclusionParams(map: MutableMap, params: List>) { - /*if (params.isEmpty()) { - return - }*/ - map["include"] = params.toCsv() - } - private fun createBaseParams(): HashMap { val map = hashMapOf() diff --git a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt index 5fc98f397..9c472cf93 100644 --- a/src/main/kotlin/com/pubnub/api/PNConfiguration.kt +++ b/src/main/kotlin/com/pubnub/api/PNConfiguration.kt @@ -7,6 +7,7 @@ import okhttp3.Authenticator import okhttp3.CertificatePinner import okhttp3.ConnectionSpec import okhttp3.logging.HttpLoggingInterceptor +import org.slf4j.LoggerFactory import java.net.Proxy import java.net.ProxySelector import java.util.* @@ -16,6 +17,8 @@ import javax.net.ssl.X509ExtendedTrustManager class PNConfiguration { + private val log = LoggerFactory.getLogger("PNConfiguration") + private companion object Constants { private const val DEFAULT_DEDUPE_SIZE = 100 private const val PRESENCE_TIMEOUT = 300 @@ -44,7 +47,7 @@ class PNConfiguration { set(value) { field = if (value < MINIMUM_PRESENCE_TIMEOUT) { - println("Presence timeout is too low. Defaulting to: $MINIMUM_PRESENCE_TIMEOUT") + log.warn("Presence timeout is too low. Defaulting to: $MINIMUM_PRESENCE_TIMEOUT") MINIMUM_PRESENCE_TIMEOUT } else value heartbeatInterval = (presenceTimeout / 2) - 1 diff --git a/src/main/kotlin/com/pubnub/api/PubNub.kt b/src/main/kotlin/com/pubnub/api/PubNub.kt index 6a44bb409..ebd25a80e 100644 --- a/src/main/kotlin/com/pubnub/api/PubNub.kt +++ b/src/main/kotlin/com/pubnub/api/PubNub.kt @@ -28,13 +28,13 @@ class PubNub(val configuration: PNConfiguration) { private companion object Constants { private const val TIMESTAMP_DIVIDER = 1000 - private const val SDK_VERSION = "0.0.2-dev" + private const val SDK_VERSION = "4.0.0-dev" private const val MAX_SEQUENCE = 65535; } private val basePathManager = BasePathManager(configuration) - internal val mapper = MapperManager() + val mapper = MapperManager() internal val retrofitManager = RetrofitManager(this) internal val publishSequenceManager = PublishSequenceManager(MAX_SEQUENCE) internal val telemetryManager = TelemetryManager() @@ -100,9 +100,6 @@ class PubNub(val configuration: PNConfiguration) { fun getSubscribedChannelGroups() = subscriptionManager.getSubscribedChannelGroups() fun unsubscribeAll() = subscriptionManager.unsubscribeAll() - // End Message Actions API - // public methods - /** * Perform Cryptographic decryption of an input string using cipher key provided by PNConfiguration * diff --git a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt index 4782441e9..b2ec58f84 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubUtil.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubUtil.kt @@ -4,6 +4,7 @@ import com.pubnub.api.vendor.Base64 import com.pubnub.api.vendor.Crypto import okhttp3.Request import okio.Buffer +import org.slf4j.LoggerFactory import java.io.IOException import java.io.UnsupportedEncodingException import java.net.URLDecoder @@ -15,10 +16,12 @@ import java.util.* import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec -class PubNubUtil { +internal class PubNubUtil { companion object A { + private val log = LoggerFactory.getLogger("PubNubUtil") + private const val CHARSET = "UTF-8" fun replaceLast(string: String, toReplace: String, replacement: String): String { @@ -131,9 +134,9 @@ class PubNubUtil { signature = "v2.$signature" } } catch (e: PubNubException) { - println("signature failed on SignatureInterceptor: $e") + log.warn("signature failed on SignatureInterceptor: $e") } catch (e: UnsupportedEncodingException) { - println("signature failed on SignatureInterceptor: $e") + log.warn("signature failed on SignatureInterceptor: $e") } return signature } @@ -174,14 +177,13 @@ class PubNubUtil { return stringifiedArguments } - internal fun preparePamArguments(encodedQueryString: String): String { + private fun preparePamArguments(encodedQueryString: String): String { return encodedQueryString.split("&") .toSortedSet() .map { pamEncode(it, true) } .joinToString("&") } - /** * Returns encoded String * @@ -198,7 +200,6 @@ class PubNubUtil { .replace("+", "%20") }.run { replace("*", "%2A") - // this } } diff --git a/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt index 5b36964c2..5a3b5ab23 100644 --- a/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt +++ b/src/main/kotlin/com/pubnub/api/builder/PubSubBuilders.kt @@ -4,8 +4,8 @@ import com.pubnub.api.managers.SubscriptionManager abstract class PubSubBuilder( protected val subscriptionManager: SubscriptionManager, - internal var channels: List = emptyList(), - internal var channelGroups: List = emptyList() + var channels: List = emptyList(), + var channelGroups: List = emptyList() ) { abstract fun execute() } @@ -14,7 +14,7 @@ class PresenceBuilder( subscriptionManager: SubscriptionManager, channels: List = emptyList(), channelGroups: List = emptyList(), - internal var connected: Boolean = false + var connected: Boolean = false ) : PubSubBuilder(subscriptionManager, channels, channelGroups) { override fun execute() { diff --git a/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt index f267c4cc8..0687d12b0 100644 --- a/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt +++ b/src/main/kotlin/com/pubnub/api/callbacks/SubscribeCallback.kt @@ -6,9 +6,6 @@ import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult -import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult -import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult -import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult abstract class SubscribeCallback { abstract fun status(pubnub: PubNub, pnStatus: PNStatus) @@ -19,11 +16,5 @@ abstract class SubscribeCallback { open fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - open fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - - open fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - - open fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} - open fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt index bf0622525..3a4640ac2 100644 --- a/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/BasePathManager.kt @@ -2,7 +2,7 @@ package com.pubnub.api.managers import com.pubnub.api.PNConfiguration -class BasePathManager(val config: PNConfiguration) { +internal class BasePathManager(private val config: PNConfiguration) { /** * for cache busting, the current subdomain number used. @@ -19,6 +19,7 @@ class BasePathManager(val config: PNConfiguration) { */ private val DEFAULT_SUBDOMAIN = "ps" + /** * default base path if a custom one is not provided. */ diff --git a/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt b/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt index 287ee41ec..16d1e90f3 100644 --- a/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/DuplicationManager.kt @@ -3,9 +3,7 @@ package com.pubnub.api.managers import com.pubnub.api.PNConfiguration import com.pubnub.api.models.server.SubscribeMessage -class DuplicationManager( - private val config: PNConfiguration -) { +internal class DuplicationManager(private val config: PNConfiguration) { private val hashHistory: ArrayList = ArrayList() @@ -14,7 +12,6 @@ class DuplicationManager( "${publishMetaData?.publishTimetoken}-${payload.hashCode()}" } - fun isDuplicate(message: SubscribeMessage) = hashHistory.contains(getKey(message)) fun addEntry(message: SubscribeMessage) { diff --git a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt index 8aa040aac..8a70b7459 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ListenerManager.kt @@ -9,7 +9,7 @@ import com.pubnub.api.models.consumer.pubsub.PNSignalResult import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult import java.util.* -class ListenerManager(val pubnub: PubNub) { +internal class ListenerManager(val pubnub: PubNub) { private val listeners = mutableListOf() fun addListener(listener: SubscribeCallback) { diff --git a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt index 848e8a8fa..2b71e118d 100644 --- a/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/ReconnectionManager.kt @@ -4,10 +4,13 @@ import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.callbacks.ReconnectionCallback import com.pubnub.api.enums.PNReconnectionPolicy +import org.slf4j.LoggerFactory import java.util.* import kotlin.math.pow -class ReconnectionManager(val pubnub: PubNub) { +internal class ReconnectionManager(val pubnub: PubNub) { + + private val log = LoggerFactory.getLogger("ReconnectionManager") private companion object { private const val LINEAR_INTERVAL = 3 @@ -66,11 +69,11 @@ class ReconnectionManager(val pubnub: PubNub) { if (timerInterval > MAX_EXPONENTIAL_BACKOFF) { timerInterval = MIN_EXPONENTIAL_BACKOFF exponentialMultiplier = 1 - println("timerInterval > MAXEXPONENTIALBACKOFF at: " + Calendar.getInstance().time.toString()) + log.info("timerInterval > MAXEXPONENTIALBACKOFF at: " + Calendar.getInstance().time.toString()) } else if (timerInterval < 1) { timerInterval = MIN_EXPONENTIAL_BACKOFF } - println("timerInterval = " + timerInterval + " at: " + Calendar.getInstance().time.toString()) + log.info("timerInterval = " + timerInterval + " at: " + Calendar.getInstance().time.toString()) } if (pnReconnectionPolicy == PNReconnectionPolicy.LINEAR) { timerInterval = LINEAR_INTERVAL @@ -89,7 +92,7 @@ class ReconnectionManager(val pubnub: PubNub) { stopHeartbeatTimer() reconnectionCallback.onReconnection() } else { - println("callTime at ${System.currentTimeMillis()}") + log.info("callTime at ${System.currentTimeMillis()}") exponentialMultiplier++ failedCalls++ registerHeartbeatTimer() @@ -97,10 +100,9 @@ class ReconnectionManager(val pubnub: PubNub) { } } - private fun isReconnectionPolicyUndefined(): Boolean { if (pnReconnectionPolicy == PNReconnectionPolicy.NONE) { - println("reconnection policy is disabled, please handle reconnection manually.") + log.info("reconnection policy is disabled, please handle reconnection manually.") return true } return false diff --git a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt index 77d87ffe1..04ec1042b 100644 --- a/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/RetrofitManager.kt @@ -9,7 +9,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import java.util.concurrent.TimeUnit -class RetrofitManager(val pubnub: PubNub) { +internal class RetrofitManager(val pubnub: PubNub) { private val transactionClientInstance: OkHttpClient by lazy { createOkHttpClient(pubnub.configuration.nonSubscribeRequestTimeout) diff --git a/src/main/kotlin/com/pubnub/api/managers/StateManager.kt b/src/main/kotlin/com/pubnub/api/managers/StateManager.kt index 47c583f36..861966ca8 100644 --- a/src/main/kotlin/com/pubnub/api/managers/StateManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/StateManager.kt @@ -6,7 +6,7 @@ import com.pubnub.api.builder.SubscribeOperation import com.pubnub.api.builder.UnsubscribeOperation import com.pubnub.api.models.SubscriptionItem -class StateManager { +internal class StateManager { /** * Contains a list of subscribed channels diff --git a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt index 70c7aa684..9a646c270 100644 --- a/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/SubscriptionManager.kt @@ -385,11 +385,9 @@ class SubscriptionManager(val pubnub: PubNub) { @Synchronized fun destroy(forceDestroy: Boolean = false) { - synchronized(this) { - disconnect() - if (forceDestroy && consumerThread != null) { - consumerThread!!.interrupt() - } + disconnect() + if (forceDestroy && consumerThread != null) { + consumerThread!!.interrupt() } } diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt deleted file mode 100644 index d4c82fdff..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNErrorData.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.pubnub.api.models.consumer - -/* -data class PNErrorData( - */ -/*val information: String?, - val throwable: Exception?*//* - - var information: String?, - var throwable: Exception? -) { -}*/ diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt index 6817815ea..d27b21efd 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNPublishResult.kt @@ -1,5 +1,5 @@ package com.pubnub.api.models.consumer -data class PNPublishResult internal constructor( +class PNPublishResult internal constructor( val timetoken: Long ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt deleted file mode 100644 index a7675e611..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNSignalResult.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.pubnub.api.models.consumer - -import com.google.gson.JsonElement -import com.pubnub.api.models.consumer.pubsub.BasePubSubResult -import com.pubnub.api.models.consumer.pubsub.MessageResult - -class PNSignalResult( - basePubSubResult: BasePubSubResult, - message: JsonElement -) : MessageResult(basePubSubResult, message) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt index 245f0d78a..ba93a2398 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/PNTimeResult.kt @@ -1,5 +1,5 @@ package com.pubnub.api.models.consumer -data class PNTimeResult internal constructor( +class PNTimeResult internal constructor( val timetoken: Long ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt index e8b971f4a..fa2ffabed 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResults.kt @@ -10,7 +10,7 @@ class PNAccessManagerGrantResult( val channelGroups: Map?> ) -class PNAccessManagerKeyData() { +class PNAccessManagerKeyData { @SerializedName("r") internal var readEnabled: Boolean = false diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt index 7f3c4bba2..85bf2716e 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNFetchMessage.kt @@ -9,10 +9,10 @@ class PNFetchMessagesResult( class PNFetchMessageItem( val message: JsonElement, val meta: JsonElement?, - val timetoken: Long, - val actions: Map>>? + val timetoken: Long ) { - + var actions: Map>>? = null + internal set } class Action( diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt index 491b433a4..1811e4dab 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/history/PNHistoryResult.kt @@ -1,6 +1,6 @@ package com.pubnub.api.models.consumer.history -data class PNHistoryResult internal constructor( +class PNHistoryResult internal constructor( val messages: List, val startTimetoken: Long, val endTimetoken: Long diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt index 78ea70228..6f2e7de01 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/presence/PNWhereNow.kt @@ -1,5 +1,5 @@ package com.pubnub.api.models.consumer.presence -data class PNWhereNowResult( +class PNWhereNowResult( val channels: List ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt index 2a7e1aaa0..f0b75df87 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.kt @@ -3,18 +3,19 @@ package com.pubnub.api.models.consumer.pubsub import com.google.gson.JsonElement open class BasePubSubResult( - internal val channel: String, - internal val subscription: String?, - internal val timetoken: Long?, - internal val userMetadata: JsonElement?, - internal val publisher: String? + val channel: String, + val subscription: String?, + val timetoken: Long?, + val userMetadata: JsonElement?, + val publisher: String? ) { - constructor(basePubSubResult: BasePubSubResult) : this( + internal constructor(basePubSubResult: BasePubSubResult) : this( basePubSubResult.channel, basePubSubResult.subscription, basePubSubResult.timetoken, basePubSubResult.userMetadata, basePubSubResult.publisher ) -} \ No newline at end of file +} + diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt index 2fb2d06ab..90a1ef695 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNMessageResult.kt @@ -2,7 +2,7 @@ package com.pubnub.api.models.consumer.pubsub import com.google.gson.JsonElement -data class PNMessageResult( - val basePubSubResult: BasePubSubResult, - val messageResult: JsonElement +class PNMessageResult( + basePubSubResult: BasePubSubResult, + messageResult: JsonElement ) : MessageResult(basePubSubResult, messageResult) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt index 40a6f9b5e..498e8760a 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.kt @@ -2,18 +2,18 @@ package com.pubnub.api.models.consumer.pubsub import com.google.gson.JsonElement -data class PNPresenceEventResult( - internal val event: String? = null, - internal val uuid: String? = null, - internal val timestamp: Long? = null, - internal val occupancy: Int? = null, - internal val state: JsonElement? = null, - internal val channel: String? = null, - internal val subscription: String? = null, - internal val timetoken: Long? = null, - internal val join: List? = null, - internal val leave: List? = null, - internal val timeout: List? = null, - internal val hereNowRefresh: Boolean? = null, - internal val userMetadata: Any? = null +class PNPresenceEventResult( + val event: String? = null, + val uuid: String? = null, + val timestamp: Long? = null, + val occupancy: Int? = null, + val state: JsonElement? = null, + val channel: String? = null, + val subscription: String? = null, + val timetoken: Long? = null, + val join: List? = null, + val leave: List? = null, + val timeout: List? = null, + val hereNowRefresh: Boolean? = null, + val userMetadata: Any? = null ) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt index b7d722786..f99c024d7 100644 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt +++ b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.kt @@ -6,7 +6,7 @@ import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult class PNMessageActionResult( result: BasePubSubResult, - event: String, + override val event: String, data: PNMessageAction ) : ObjectResult(result, event, data) { diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt deleted file mode 100644 index 46b08dc73..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNMembershipResult.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.pubnub.api.models.consumer.pubsub.objects - -import com.google.gson.JsonElement -import com.pubnub.api.models.consumer.pubsub.BasePubSubResult - -class PNMembershipResult( - override val result: BasePubSubResult, - override val event: String, - override val data: JsonElement -) : ObjectResult( - result, - event, - data -) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt deleted file mode 100644 index 6dc4edd7f..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNSpaceResult.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.pubnub.api.models.consumer.pubsub.objects - -import com.google.gson.JsonElement -import com.pubnub.api.models.consumer.pubsub.BasePubSubResult - -class PNSpaceResult( - override val result: BasePubSubResult, - override val event: String, - override val data: JsonElement -) : ObjectResult( - result, - event, - data -) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt b/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt deleted file mode 100644 index 07b62aee4..000000000 --- a/src/main/kotlin/com/pubnub/api/models/consumer/pubsub/objects/PNUserResult.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.pubnub.api.models.consumer.pubsub.objects - -import com.google.gson.JsonElement -import com.pubnub.api.models.consumer.pubsub.BasePubSubResult - -class PNUserResult( - override val result: BasePubSubResult, - override val event: String, - override val data: JsonElement -) : ObjectResult( - result, - event, - data -) \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt index 4332f90c4..4d99115e1 100644 --- a/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt +++ b/src/main/kotlin/com/pubnub/api/workers/SubscribeMessageWorker.kt @@ -19,15 +19,18 @@ import com.pubnub.api.models.consumer.pubsub.objects.ObjectPayload import com.pubnub.api.models.server.PresenceEnvelope import com.pubnub.api.models.server.SubscribeMessage import com.pubnub.api.vendor.Crypto +import org.slf4j.LoggerFactory import java.util.concurrent.LinkedBlockingQueue -class SubscribeMessageWorker( +internal class SubscribeMessageWorker( val pubnub: PubNub, val listenerManager: ListenerManager, val queue: LinkedBlockingQueue, val duplicationManager: DuplicationManager ) : Runnable { + private val log = LoggerFactory.getLogger("SubscribeMessageWorker") + private var running = false companion object { @@ -49,7 +52,8 @@ class SubscribeMessageWorker( processIncomingPayload(queue.take()) } catch (e: InterruptedException) { running = false - print("take message interrupted!") + log.trace("take message interrupted!") + e.printStackTrace() } } } @@ -104,7 +108,7 @@ class SubscribeMessageWorker( val extractedMessage = processMessage(message) if (extractedMessage == null) { - println("unable to parse payload on #processIncomingMessages") + log.debug("unable to parse payload on #processIncomingMessages") } val result = BasePubSubResult( From ac7a1910d97e1014bd769acb71351c713c6e1c1d Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:28:20 +0200 Subject: [PATCH 93/98] Respect key settings when storing messages --- src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt index c746a607f..c33eca924 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/pubsub/Publish.kt @@ -15,7 +15,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { lateinit var channel: String lateinit var message: Any lateinit var meta: Any - var shouldStore = false + var shouldStore: Boolean? = null var usePost = false var replicate = true var ttl: Int? = null @@ -44,7 +44,7 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { queryParams["meta"] = pubnub.mapper.toJson(meta) } - queryParams["store"] = if (shouldStore) "1" else "0" + shouldStore?.run { queryParams["store"] = if (this) "1" else "0" } ttl?.let { queryParams["ttl"] = it.toString() } @@ -79,8 +79,6 @@ class Publish(pubnub: PubNub) : Endpoint, PNPublishResult>(pubnub) { stringifiedMessage = "\"$stringifiedMessage\"" } - stringifiedMessage = /*PubNubUtil.urlEncode*/(stringifiedMessage) - return pubnub.retrofitManager.publishService.publish( pubnub.configuration.publishKey, pubnub.configuration.subscribeKey, From 426998237253457412177c42fdf1062e87495787 Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:30:33 +0200 Subject: [PATCH 94/98] Cleanup --- src/main/kotlin/com/pubnub/api/Endpoint.kt | 3 - src/main/kotlin/com/pubnub/api/PubNubError.kt | 16 ++-- .../com/pubnub/api/endpoints/FetchMessages.kt | 24 ++++-- .../pubnub/api/endpoints/presence/HereNow.kt | 4 +- .../com/pubnub/api/managers/MapperManager.kt | 82 ++++++++++++++++++- 5 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/pubnub/api/Endpoint.kt b/src/main/kotlin/com/pubnub/api/Endpoint.kt index 076c98219..4442a5a5b 100644 --- a/src/main/kotlin/com/pubnub/api/Endpoint.kt +++ b/src/main/kotlin/com/pubnub/api/Endpoint.kt @@ -401,9 +401,6 @@ abstract class Endpoint(protected val pubnub: PubNub) { } } - // protected abstract fun getAffectedChannels(): List - // protected abstract fun getAffectedChannelGroups(): List - protected open fun getAffectedChannels() = emptyList() protected open fun getAffectedChannelGroups(): List = emptyList() diff --git a/src/main/kotlin/com/pubnub/api/PubNubError.kt b/src/main/kotlin/com/pubnub/api/PubNubError.kt index e2b00b341..766607c9e 100644 --- a/src/main/kotlin/com/pubnub/api/PubNubError.kt +++ b/src/main/kotlin/com/pubnub/api/PubNubError.kt @@ -142,24 +142,18 @@ enum class PubNubError(val code: Int, val message: String) { "Message action timetoken is missing." ), + HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS( + 163, + "History can return message action data for a single channel only. Either pass a single channel or disable the includeMessageActions flag." + ), + PUSH_TOPIC_MISSING( 164, "Push notification topic is missing. Required only if push type is APNS2." - ), - - UNINITIALIZED_PROPERTY_ACCESS( - 998, - "Uninitialized Property Access" - ), - - X( - 999, - "X ERROR" ); override fun toString(): String { return "PubNubError(name=$name, code=$code, message='$message')" } - } \ No newline at end of file diff --git a/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt b/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt index 431061fbb..e9961e539 100644 --- a/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt +++ b/src/main/kotlin/com/pubnub/api/endpoints/FetchMessages.kt @@ -7,20 +7,22 @@ import com.pubnub.api.models.consumer.history.PNFetchMessageItem import com.pubnub.api.models.consumer.history.PNFetchMessagesResult import com.pubnub.api.models.server.FetchMessagesEnvelope import com.pubnub.api.vendor.Crypto +import org.slf4j.LoggerFactory import retrofit2.Call import retrofit2.Response import java.util.* class FetchMessages(pubnub: PubNub) : Endpoint(pubnub) { + private val log = LoggerFactory.getLogger("FetchMessages") + private companion object { private const val DEFAULT_MESSAGES = 1 private const val MAX_MESSAGES = 25 - private const val MAX_MESSAGES_ACTIONS = 100 } lateinit var channels: List - var maximumPerChannel: Int = DEFAULT_MESSAGES + var maximumPerChannel = 0 var start: Long? = null var end: Long? = null var includeMeta = false @@ -38,12 +40,12 @@ class FetchMessages(pubnub: PubNub) : Endpoint maximumPerChannel = DEFAULT_MESSAGES maximumPerChannel > MAX_MESSAGES -> maximumPerChannel = MAX_MESSAGES } - print("maximumPerChannel param defaulting to $maximumPerChannel") + log.info("maximumPerChannel param defaulting to $maximumPerChannel") } } else { if (maximumPerChannel !in DEFAULT_MESSAGES..MAX_MESSAGES) { - maximumPerChannel = MAX_MESSAGES_ACTIONS - print("maximumPerChannel param defaulting to $maximumPerChannel") + maximumPerChannel = MAX_MESSAGES + log.info("maximumPerChannel param defaulting to $maximumPerChannel") } } } @@ -71,6 +73,9 @@ class FetchMessages(pubnub: PubNub) : Endpoint 1) { + throw PubNubException(PubNubError.HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS) + } pubnub.retrofitManager.historyService.fetchMessagesWithActions( subKey = pubnub.configuration.subscribeKey, channel = channels.first(), @@ -90,9 +95,12 @@ class FetchMessages(pubnub: PubNub) : Endpoint, PNHereNowResult>(pubnub) { @@ -22,8 +22,6 @@ class HereNow(pubnub: PubNub) : Endpoint, PNHereNowResult> private fun isGlobalHereNow() = channels.isEmpty() && channelGroups.isEmpty() - // todo try here now without sub key - override fun getAffectedChannels() = channels override fun getAffectedChannelGroups() = channelGroups diff --git a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt index e995928b6..4d9c424c5 100644 --- a/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt +++ b/src/main/kotlin/com/pubnub/api/managers/MapperManager.kt @@ -6,6 +6,9 @@ import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import com.pubnub.api.PubNubError import com.pubnub.api.PubNubException +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject import retrofit2.Converter import retrofit2.converter.gson.GsonConverterFactory import java.lang.reflect.Type @@ -40,6 +43,8 @@ class MapperManager { .registerTypeAdapter(Boolean::class.javaObjectType, booleanAsIntAdapter) .registerTypeAdapter(Boolean::class.javaPrimitiveType, booleanAsIntAdapter) .registerTypeAdapter(Boolean::class.java, booleanAsIntAdapter) + .registerTypeAdapter(JSONObject::class.java, JSONObjectAdapter()) + .registerTypeAdapter(JSONArray::class.java, JSONArrayAdapter()) .create() converterFactory = GsonConverterFactory.create(objectMapper) } @@ -109,10 +114,9 @@ class MapperManager { return this.objectMapper.fromJson(input, clazz) as T } - /*@Throws(PubNubException::class) - fun convertValue(obj: Any, clazz: Class): T { - return fromJson(toJson(obj), clazz) - }*/ + fun convertValue(o: Any?, clazz: Class?): T { + return this.objectMapper.fromJson(toJson(o), clazz) as T + } fun toJson(input: Any?): String { return try { @@ -123,4 +127,74 @@ class MapperManager { } } } + + private class JSONObjectAdapter : JsonSerializer, JsonDeserializer { + override fun serialize( + src: JSONObject?, + typeOfSrc: Type?, + context: JsonSerializationContext + ): JsonElement? { + if (src == null) { + return null + } + val jsonObject = JsonObject() + val keys: Iterator = src.keys() + while (keys.hasNext()) { + val key = keys.next() + val value: Any = src.opt(key) + val jsonElement = context.serialize(value, value.javaClass) + jsonObject.add(key, jsonElement) + } + return jsonObject + } + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): JSONObject? { + return if (json == null) { + null + } else try { + JSONObject(json.toString()) + } catch (e: JSONException) { + e.printStackTrace() + throw JsonParseException(e) + } + } + } + + private class JSONArrayAdapter : JsonSerializer, JsonDeserializer { + override fun serialize( + src: JSONArray?, + typeOfSrc: Type?, + context: JsonSerializationContext + ): JsonElement? { + if (src == null) { + return null + } + val jsonArray = JsonArray() + for (i in 0 until src.length()) { + val `object`: Any = src.opt(i) + val jsonElement = context.serialize(`object`, `object`.javaClass) + jsonArray.add(jsonElement) + } + return jsonArray + } + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): JSONArray? { + return if (json == null) { + null + } else try { + JSONArray(json.toString()) + } catch (e: JSONException) { + e.printStackTrace() + throw JsonParseException(e) + } + } + } } From 8f263b1ec93d6fbfbba00affe4634da4c757263c Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:30:54 +0200 Subject: [PATCH 95/98] Add integration tests --- .../GroupManagementIntegrationTests.kt | 116 ++++ .../integration/HeartbeatIntegrationTest.kt | 103 ++++ .../api/integration/HistoryIntegrationTest.kt | 532 ++++++++++++++++ .../MessageActionsIntegrationTest.kt | 572 ++++++++++++++++++ .../PresenceEventsIntegrationTests.kt | 119 ++++ .../integration/PresenceIntegrationTests.kt | 252 ++++++++ .../integration/PublishIntegrationTests.kt | 373 ++++++++++++ .../api/integration/PushIntegrationTest.kt | 111 ++++ .../PushPayloadHelperIntegrationTest.kt | 63 ++ .../api/integration/SignalIntegrationTests.kt | 121 ++++ .../StorageAndPlaybackIntegrationTests.kt | 132 ++++ .../integration/SubscribeIntegrationTests.kt | 118 ++++ .../api/integration/TimeIntegrationTest.kt | 25 + 13 files changed, 2637 insertions(+) create mode 100644 src/test/kotlin/com/pubnub/api/integration/GroupManagementIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/HeartbeatIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/MessageActionsIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PresenceEventsIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PresenceIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PushIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/PushPayloadHelperIntegrationTest.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/SignalIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/SubscribeIntegrationTests.kt create mode 100644 src/test/kotlin/com/pubnub/api/integration/TimeIntegrationTest.kt diff --git a/src/test/kotlin/com/pubnub/api/integration/GroupManagementIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/GroupManagementIntegrationTests.kt new file mode 100644 index 000000000..7cdc3bb08 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/GroupManagementIntegrationTests.kt @@ -0,0 +1,116 @@ +package com.pubnub.api.integration + +import com.pubnub.api.suite.await +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class GroupManagementIntegrationTests : BaseIntegrationTest() { + + lateinit var expectedChannel1: String + lateinit var expectedChannel2: String + lateinit var expectedChannel3: String + + lateinit var expectedGroup: String + + override fun onBefore() { + expectedChannel1 = randomValue() + expectedChannel2 = randomValue() + expectedChannel3 = randomValue() + expectedGroup = randomValue(8) + } + + @Test + fun testRemoveChannelsFromGroup() { + addChannelsToGroup() + + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = expectedGroup + channels = listOf(expectedChannel1, expectedChannel2, expectedChannel3) + }.await { _, status -> + assertFalse(status.error) + assertEquals(3, status.affectedChannels.size) + assertEquals(0, pubnub.getSubscribedChannels().size) + assertEquals(1, status.affectedChannelGroups.size) + assertEquals(0, pubnub.getSubscribedChannelGroups().size) + } + } + + @Test + fun testRemoveChannelFromGroup() { + addChannelsToGroup() + + pubnub.removeChannelsFromChannelGroup().apply { + channelGroup = expectedGroup + channels = listOf(expectedChannel1) + }.await { _, status -> + assertFalse(status.error) + } + } + + @Test + fun testSubscribeToChannelGroup() { + addChannelsToGroup() + + pubnub.subscribe().apply { + channelGroups = listOf(expectedGroup) + withPresence = true + }.execute() + + wait() + + assertTrue(pubnub.getSubscribedChannelGroups().contains(expectedGroup)) + } + + @Test + fun testUnsubscribeFromChannelGroup() { + addChannelsToGroup() + + pubnub.subscribe().apply { + channelGroups = listOf(expectedGroup) + withPresence = true + }.execute() + + wait() + + pubnub.unsubscribe().apply { + channelGroups = listOf(expectedGroup) + }.execute() + + wait() + + assertTrue(pubnub.getSubscribedChannelGroups().isEmpty()) + } + + @Test + fun testGetAllChannelsFrogroup() { + addChannelsToGroup() + + pubnub.listChannelsForChannelGroup().apply { + channelGroup = expectedGroup + }.await { result, status -> + assertFalse(status.error) + assertEquals(3, result!!.channels.size) + } + } + + @Test + fun testAddChannelsToGroup() { + pubnub.addChannelsToChannelGroup().apply { + channelGroup = expectedGroup + channels = listOf(expectedChannel1, expectedChannel2, expectedChannel3) + }.await { _, status -> + assertFalse(status.error) + assertEquals(1, status.affectedChannelGroups.size) + assertEquals(3, status.affectedChannels.size) + } + } + + private fun addChannelsToGroup() { + pubnub.addChannelsToChannelGroup().apply { + channelGroup = expectedGroup + channels = listOf(expectedChannel1, expectedChannel2, expectedChannel3) + }.await { _, status -> + assertFalse(status.error) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/HeartbeatIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/HeartbeatIntegrationTest.kt new file mode 100644 index 000000000..bca323af7 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/HeartbeatIntegrationTest.kt @@ -0,0 +1,103 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class HeartbeatIntegrationTest : BaseIntegrationTest() { + + lateinit var expectedChannel: String + + override fun onBefore() { + expectedChannel = randomValue() + } + + @Test + fun testStateWithHeartbeat() { + val hits = AtomicInteger() + val expectedStatePayload = generatePayload() + + val observer = createPubNub().apply { + configuration.uuid = "observer_${System.currentTimeMillis()}" + } + + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatInterval = 4 + + observer.addListener(object : SubscribeCallback() { + + override fun status(p: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation + && pnStatus.affectedChannels.contains(expectedChannel) + ) { + pubnub.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute() + } + } + + override fun presence(p: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (pnPresenceEventResult.uuid.equals(pubnub.configuration.uuid) + && pnPresenceEventResult.channel.equals(expectedChannel) + ) { + when (pnPresenceEventResult.event) { + "state-change" -> { + assertEquals(expectedStatePayload, pnPresenceEventResult.state) + hits.incrementAndGet() + pubnub.disconnect() + } + "join" -> { + if (pnPresenceEventResult.state == null) { + hits.incrementAndGet() + val stateSet = AtomicBoolean() + pubnub.setPresenceState().apply { + state = expectedStatePayload + channels = Arrays.asList(expectedChannel) + + }.async { result, status -> + assertFalse(status.error) + assertEquals(expectedStatePayload, result!!.state) + hits.incrementAndGet() + stateSet.set(true) + } + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilTrue(stateSet) + } else { + assertEquals(expectedStatePayload, pnPresenceEventResult.state) + hits.incrementAndGet() + } + + } + "timeout" -> { + pubnub.reconnect(); + } + } + } + } + + }) + + observer.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute(); + + Awaitility.await() + .atMost(40, TimeUnit.SECONDS) + .untilAtomic(hits, IsEqual.equalTo(4)); + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt new file mode 100644 index 000000000..5410afba0 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/HistoryIntegrationTest.kt @@ -0,0 +1,532 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.param +import com.pubnub.api.suite.await +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class HistoryIntegrationTest : BaseIntegrationTest() { + + @Test + fun testHistorySingleChannel() { + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + pubnub.history().apply { + channel = expectedChannelName + }.sync()!!.run { + messages.forEach { + assertNotNull(it.entry) + assertNull(it.meta) + assertNull(it.timetoken) + } + } + } + + @Test + fun testHistorySingleChannel_Timetoken() { + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + pubnub.history().apply { + channel = expectedChannelName + includeTimetoken = true + }.sync()!!.run { + this.messages.forEach { + assertNotNull(it.entry) + assertNotNull(it.timetoken) + assertNull(it.meta) + } + } + } + + @Test + fun testHistorySingleChannel_Meta() { + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + pubnub.history().apply { + channel = expectedChannelName + includeMeta = true + }.sync()!!.run { + messages.forEach { + assertNotNull(it.entry) + assertNull(it.timetoken) + assertNotNull(it.meta) + } + } + } + + @Test + fun testHistorySingleChannel_Meta_Timetoken() { + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + pubnub.history().apply { + channel = expectedChannelName + includeMeta = true + includeTimetoken = true + }.sync()!!.run { + messages.forEach { + assertNotNull(it.entry) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + } + } + } + + @Test + fun testFetchSingleChannel() { + val expectedChannelName = randomValue() + + publishMixed(pubnub, 10, expectedChannelName) + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannelName) + maximumPerChannel = 25 + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNull(it.meta) + assertNull(it.actions) + } + } + } + + @Test + fun testFetchSingleChannel_Meta() { + val expectedChannelName = randomValue() + publishMixed(pubnub, 10, expectedChannelName) + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannelName) + maximumPerChannel = 25 + includeMeta = true + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + assertNull(it.actions) + } + } + } + + @Test + fun testFetchSingleChannel_Actions() { + val expectedChannelName = randomValue() + val results = publishMixed(pubnub, 120, expectedChannelName) + + // todo check with executorservice + + pubnub.addMessageAction().apply { + channel = expectedChannelName + messageAction = (PNMessageAction( + type = "reaction", + value = emoji(), + messageTimetoken = results[0].timetoken + )) + }.sync()!! + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannelName) + maximumPerChannel = 25 + includeMessageActions = true + includeMeta = false + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNull(it.meta) + if (it.timetoken == results[0].timetoken) { + assertNotNull(it.actions) + } else { + assertTrue(it.actions!!.isEmpty()) + } + } + } + } + + @Test + fun testFetchSingleChannel_ActionsMeta() { + val expectedChannelName = randomValue() + val results = publishMixed(pubnub, 2, expectedChannelName) + + pubnub.addMessageAction().apply { + channel = expectedChannelName + messageAction = PNMessageAction( + type = "reaction", + value = emoji(), + messageTimetoken = results[0].timetoken + ) + }.sync()!! + + wait() + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannelName) + maximumPerChannel = 25 + includeMessageActions = true + includeMeta = true + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + if (it.timetoken == results[0].timetoken) { + assertNotNull(it.actions) + } else { + assertTrue(it.actions!!.isEmpty()) + } + } + } + } + + @Test + fun testFetchMultiChannel() { + val expectedChannelNames = generateSequence { randomValue() } + .take(2).toList() + .also { + it.forEach { channel -> + publishMixed(pubnub, 10, channel) + } + } + + pubnub.fetchMessages().apply { + channels = expectedChannelNames + maximumPerChannel = 25 + }.sync()!!.run { + expectedChannelNames.forEach { expectedChannel -> + channels[expectedChannel]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNull(it.meta) + assertNull(it.actions) + } + } + } + + } + + @Test + fun testFetchSingleChannel_NoLimit() { + val expectedChannelName = randomValue() + assertEquals(10, publishMixed(pubnub, 10, expectedChannelName).size) + + wait() + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannelName) + }.sync()!!.run { + assertEquals(1, channels[expectedChannelName]!!.size) + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNull(it.meta) + assertNull(it.actions) + } + } + } + + @Test + fun testFetchSingleChannel_OverflowLimit() { + val expectedChannelName = randomValue() + + assertEquals(10, publishMixed(pubnub, 10, expectedChannelName).size) + + wait() + + pubnub.fetchMessages().apply { + channels = (listOf(expectedChannelName)) + maximumPerChannel = 100 + }.sync()!!.apply { + assertEquals(10, channels[expectedChannelName]!!.size) + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNull(it.meta) + assertNull(it.actions) + } + } + } + + @Test + fun testHistorySingleChannel_IncludeAll_Crypto() { + val expectedCipherKey = randomValue() + pubnub.configuration.cipherKey = expectedCipherKey + + val observer = createPubNub().apply { + configuration.cipherKey = expectedCipherKey + } + + assertEquals(pubnub.configuration.cipherKey, observer.configuration.cipherKey) + + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + observer.history().apply { + channel = expectedChannelName + includeTimetoken = true + includeMeta = true + }.sync()!!.run { + messages.forEach { + assertNotNull(it.entry) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + assertTrue(it.entry.toString().contains("_msg")) + } + } + } + + @Test + fun testFetchSingleChannel_IncludeAll_Crypto() { + val expectedCipherKey = randomValue() + pubnub.configuration.cipherKey = expectedCipherKey + + val observer = createPubNub().apply { + configuration.cipherKey = expectedCipherKey + } + + assertEquals(pubnub.configuration.cipherKey, observer.configuration.cipherKey) + + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + assertEquals( + expectedMessageCount, + publishMixed(pubnub, expectedMessageCount, expectedChannelName).size + ) + + observer.fetchMessages().apply { + channels = listOf(expectedChannelName) + includeMeta = true + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + assertNull(it.actions) + assertTrue(it.message.toString().contains("_msg")) + } + } + } + + @Test + fun testFetchSingleChannel_WithActions_IncludeAll_Crypto() { + val expectedCipherKey = randomValue() + pubnub.configuration.cipherKey = expectedCipherKey + + val observer = createPubNub().apply { + configuration.cipherKey = expectedCipherKey + } + + assertEquals(pubnub.configuration.cipherKey, observer.configuration.cipherKey) + + val expectedChannelName = randomValue() + val expectedMessageCount = 10 + + val mixed = publishMixed(pubnub, expectedMessageCount, expectedChannelName) + assertEquals(expectedMessageCount, mixed.size) + + val messagesWithActions = mutableListOf() + + mixed.forEachIndexed { i, it -> + if (i % 2 == 0) { + val reaction = pubnub.addMessageAction().apply { + channel = expectedChannelName + messageAction = PNMessageAction( + type = "reaction", + value = emoji(), + messageTimetoken = it.timetoken + ) + }.sync()!! + messagesWithActions.add(reaction.messageTimetoken) + } + } + + observer.fetchMessages().apply { + channels = listOf(expectedChannelName) + includeMeta = true + includeMessageActions = true + }.sync()!!.run { + channels[expectedChannelName]!!.forEach { + assertNotNull(it.message) + assertNotNull(it.timetoken) + assertNotNull(it.meta) + if (messagesWithActions.contains(it.timetoken)) { + assertNotNull(it.actions) + } else { + assertTrue(it.actions!!.isEmpty()) + } + assertTrue(it.message.toString().contains("_msg")) + } + } + } + + @Test + fun testFetchMultiChannel_WithMessageActions_Exception() { + try { + pubnub.fetchMessages().apply { + channels = listOf(randomValue(), randomValue()) + includeMessageActions = true + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS, e) + } + } + + @Test + fun testFetchSingleChannel_NoActions_Limit_Default() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + }.await { _, status -> + assertEquals("1", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_NoActions_Limit_Low() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + maximumPerChannel = -1 + }.await { _, status -> + assertEquals("1", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_NoActions_Limit_Valid() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + maximumPerChannel = 15 + }.await { _, status -> + assertEquals("15", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_NoActions_Limit_High() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + maximumPerChannel = 100 + }.await { _, status -> + assertEquals("25", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_WithActions_Limit_Default() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + includeMessageActions = true + }.await { _, status -> + assertEquals("25", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_WithActions_Limit_Low() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + includeMessageActions = true + maximumPerChannel = -1 + }.await { _, status -> + assertEquals("25", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_WithActions_Limit_High() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + includeMessageActions = true + maximumPerChannel = 200 + }.await { _, status -> + assertEquals("25", status.param("max")) + } + } + + @Test + fun testFetchSingleChannel_WithActions_Limit_Valid() { + pubnub.fetchMessages().apply { + channels = listOf(randomValue()) + includeMessageActions = true + maximumPerChannel = 15 + }.await { result, status -> + assertEquals("15", status.param("max")) + } + } + + @Test + fun testEmptyMeta() { + val expectedChannel = randomValue() + + // publish a message without any metadata + pubnub.publish().apply { + message = randomValue() + channel = expectedChannel + }.sync()!! + + wait() + + // /v2/history + pubnub.history().apply { + channel = expectedChannel + includeMeta = true + }.sync()!!.run { + assertEquals(1, messages.size) + assertNotNull(messages[0].meta) + } + + // /v3/history + pubnub.fetchMessages().apply { + channels = listOf(expectedChannel) + includeMeta = true + }.sync()!!.run { + assertEquals(1, channels[expectedChannel]!!.size) + assertNotNull(channels[expectedChannel]!![0].meta) + } + + // /v3/history-with-actions + pubnub.fetchMessages().apply { + channels = listOf(expectedChannel) + includeMeta = true + includeMessageActions = true + }.sync()!!.run { + assertEquals(1, channels[expectedChannel]!!.size) + assertNotNull(channels[expectedChannel]!![0].meta) + } + + // three responses from three different APIs will return a non-null meta field + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/MessageActionsIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/MessageActionsIntegrationTest.kt new file mode 100644 index 000000000..dd32844e6 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/MessageActionsIntegrationTest.kt @@ -0,0 +1,572 @@ +package com.pubnub.api.integration + +import com.pubnub.api.* +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.endpoints.message_actions.GetMessageActions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.models.consumer.PNPublishResult +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.history.Action +import com.pubnub.api.models.consumer.history.PNFetchMessageItem +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult +import com.pubnub.api.models.consumer.message_actions.PNMessageAction +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult +import com.pubnub.api.suite.await +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Consumer + +class MessageActionsIntegrationTest : BaseIntegrationTest() { + + lateinit var publishResult: PNPublishResult + lateinit var expectedChannel: String + + override fun onBefore() { + expectedChannel = randomValue() + + publishResult = pubnub.publish().apply { + channel = expectedChannel + message = generatePayload() + }.sync()!! + } + + @Test + fun testAddMessageAction() { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = + PNMessageAction( + type = "someother", + value = "smiley", + messageTimetoken = publishResult.timetoken + ) + }.await { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNAddMessageAction, status.operation) + + } + } + + @Test + fun testGetMessageAction() { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = + PNMessageAction( + type = randomValue(), + value = randomValue(), + messageTimetoken = publishResult.timetoken + ) + }.sync()!! + + pubnub.getMessageActions().apply { + channel = expectedChannel + }.await { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNGetMessageActions, status.operation) + } + } + + @Test + fun testDeleteMessageAction() { + val expectedValue = UUID.randomUUID().toString() + + val addMessageActionResult = pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = ( + PNMessageAction( + type = "REACTION", + value = expectedValue, + messageTimetoken = publishResult.timetoken + )) + }.sync()!! + + pubnub.removeMessageAction().apply { + messageTimetoken = publishResult.timetoken + actionTimetoken = addMessageActionResult.actionTimetoken + channel = expectedChannel + }.await { _, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNDeleteMessageAction, status.operation) + } + } + + @Test + fun testAddGetMessageAction() { + val expectedValue = UUID.randomUUID().toString() + + val addMessageActionResult = pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "REACTION", + value = expectedValue, + messageTimetoken = publishResult.timetoken + ) + }.sync() + + pubnub.getMessageActions().apply { + channel = expectedChannel + + }.await { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNGetMessageActions, status.operation) + assertEquals(1, result!!.actions.size) + assertEquals(result.actions[0].actionTimetoken, addMessageActionResult!!.actionTimetoken) + } + } + + @Test + fun testAddGetMessageAction_Bulk() { + val expectedMessageCount = 10 + val expectedChannel = randomValue() + + publishMixed(pubnub, expectedMessageCount, expectedChannel).forEach { pnPublishResult -> + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "reaction", + value = emoji(), + messageTimetoken = pnPublishResult.timetoken + ) + }.sync()!! + } + + pubnub.getMessageActions().apply { + channel = expectedChannel + }.await { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNGetMessageActions, status.operation) + assertEquals(expectedMessageCount, result!!.actions.size) + } + } + + @Test + @Throws(PubNubException::class) + fun testAddGetMessageAction_Bulk_Pagination() { + val expectedChannelName = randomValue() + val messageCount = 10 + val messages = publishMixed(pubnub, messageCount, expectedChannelName) + + assertEquals(10, messages.size) + + messages.forEachIndexed { index, i -> + pubnub.addMessageAction().apply { + channel = expectedChannelName + messageAction = PNMessageAction( + type = "reaction", + value = "${index + 1}_${emoji()}", + messageTimetoken = i.timetoken + ) + }.sync() + } + + val success = AtomicBoolean() + val count = AtomicInteger() + + page(expectedChannelName, System.currentTimeMillis() * 10_000, object : Callback { + override fun onMore(actions: List) { + count.set(count.get() + actions.size) + } + + override fun onDone() { + success.set(count.get() == messageCount) + } + }) + + success.listen() + } + + interface Callback { + fun onMore(actions: List) + fun onDone() + } + + fun page(channel: String, start: Long, callback: Callback) { + pubnub.getMessageActions().apply { + this.channel = channel + this.start = start + this.limit = 5 + + }.async { result, status -> + if (!status.error && !result!!.actions.isEmpty()) { + callback.onMore(result.actions) + page(channel, result.actions[0].actionTimetoken!!, callback) + } else { + callback.onDone() + } + } + } + + @Test + fun loopActions() { + val publishList = mutableListOf() + + val totalMessageCount = 10 + + for (i in 1..totalMessageCount) { + pubnub.publish().apply { + channel = expectedChannel + message = "${i}_${randomValue()}" + }.sync()!!.run { + publishList.add(this) + } + } + + assertEquals(totalMessageCount, publishList.size) + + publishList.forEachIndexed { i, it -> + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = + PNMessageAction( + type = "REACTION", + value = "${(i + 1)}_${randomValue(5)}", + messageTimetoken = it.timetoken + ) + }.sync()!! + } + + val pnActionList = mutableListOf() + + pageActions(3, expectedChannel, null, object : Callback { + override fun onMore(actions: List) { + pnActionList.addAll(actions.reversed()) + } + + override fun onDone() { + val tts = pnActionList.map { it.actionTimetoken!! }.toList() + assertTrue(tts == tts.sorted().reversed()) + } + }) + } + + fun pageActions(chunk: Int, channel: String, start: Long?, callback: Callback) { + val builder: GetMessageActions = pubnub.getMessageActions().apply { + this.limit = chunk + this.channel = channel + } + if (start != null) { + builder.start = start + } + val messageActionsResult = builder.sync() + if (messageActionsResult!!.actions.isNotEmpty()) { + callback.onMore(messageActionsResult.actions) + pageActions(chunk, channel, messageActionsResult.actions[0].actionTimetoken, callback) + } else { + callback.onDone() + } + } + + @Test + fun testFetchHistory() { + val expectedChannel = randomValue() + val expectedMessageCount = 10 + val publishResultList = publishMixed(pubnub, expectedMessageCount, expectedChannel) + + + publishResultList.forEachIndexed { i, it -> + if (i % 2 == 0 && i % 3 == 0) { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "receipt", + value = emoji(), + messageTimetoken = publishResultList[i].timetoken + ) + }.sync()!! + } + if (i % 3 == 0) { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "receipt", + value = emoji(), + messageTimetoken = publishResultList[i].timetoken + ) + }.sync()!! + } + if (i % 2 == 0) { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "receipt", + value = emoji(), + messageTimetoken = publishResultList[i].timetoken + ) + }.sync()!! + } + if (i % 5 == 0) { + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "fiver", + value = emoji(), + messageTimetoken = publishResultList[i].timetoken + ) + }.sync()!! + } + } + + val fetchMessagesResultWithActions: PNFetchMessagesResult = pubnub.fetchMessages().apply { + channels = Collections.singletonList(expectedChannel) + includeMeta = true + includeMessageActions = true + }.sync()!! + + fetchMessagesResultWithActions.channels.forEach { (channel, item: List) -> + println("Channel: " + channel + ". Messages: " + item.size) + item.forEach(Consumer { pnFetchMessageItem: PNFetchMessageItem -> + println("\tMessage: " + pnFetchMessageItem.message) + println("\tTimetoken: " + pnFetchMessageItem.timetoken) + println("\tMeta: " + pnFetchMessageItem.meta) + if (pnFetchMessageItem.actions == null) { + println("\t\tNo actions here.") + return@Consumer + } + println("\t\tTotal action types: " + pnFetchMessageItem.actions!!.size) + pnFetchMessageItem.actions!!.forEach { (type, map: HashMap>) -> + println("\t\t\tAction type: $type") + map.forEach { (value, actions: List) -> + println("\t\t\t\tAction value: $value") + actions.forEach(Consumer { action: Action -> + println("\t\t\t\tAction uuid: " + action.uuid) + println("\t\t\t\tAction timetoken: " + action.actionTimetoken) + }) + } + } + println("--------------------") + }) + } + fetchMessagesResultWithActions.channels.forEach { (s: String?, pnFetchMessageItems: List) -> + pnFetchMessageItems.forEach( + Consumer { pnFetchMessageItem: PNFetchMessageItem -> + assertNotNull(pnFetchMessageItem.actions) + assertTrue(pnFetchMessageItem.actions!!.size >= 0) + } + ) + } + val fetchMessagesResultNoActions: PNFetchMessagesResult = pubnub.fetchMessages().apply { + channels = listOf(expectedChannel) + }.sync()!! + fetchMessagesResultNoActions.channels.forEach { (s: String?, pnFetchMessageItems: List) -> + pnFetchMessageItems.forEach( + Consumer { pnFetchMessageItem: PNFetchMessageItem -> + assertNull( + pnFetchMessageItem.actions + ) + } + ) + } + } + + @Test + fun testActionReceive() { + val expectedChannelName = randomValue() + val expectedMessageCount = 1 + + val publishResultList = mutableListOf() + + repeat(expectedMessageCount) { + pubnub.publish().apply { + channel = expectedChannelName + message = "${it}_msg" + if (it % 2 == 0) meta = generateMap() + }.sync()!!.run { + publishResultList.add(this) + } + } + + assertEquals(expectedMessageCount, publishResultList.size) + + val actionsCount = AtomicInteger() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.category == PNStatusCategory.PNConnectedCategory) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation) { + publishResultList.forEach { + pubnub.addMessageAction().apply { + channel = expectedChannelName + messageAction = PNMessageAction( + type = "reaction", + value = emoji(), + messageTimetoken = it.timetoken + ) + }.sync()!! + } + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + failTest() + } + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + failTest() + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { + failTest() + } + + override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) { + assertEquals(expectedChannelName, pnMessageActionResult.channel) + actionsCount.incrementAndGet(); + } + }) + + pubnub.subscribe().apply { + channels = Collections.singletonList(expectedChannelName) + withPresence = true + + }.execute() + + Awaitility.await() + .atMost(Durations.TEN_SECONDS) + .untilAtomic(actionsCount, IsEqual.equalTo(expectedMessageCount)); + + } + + @Test + fun testAddAction_NoChannel() { + try { + pubnub.addMessageAction() + .sync() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testAddAction_NoMessageActionObject() { + try { + pubnub.addMessageAction().apply { + channel = randomValue() + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_MISSING, e) + } + } + + @Test + fun testAddAction_NoMessageActionType() { + try { + pubnub.addMessageAction().apply { + channel = randomValue() + messageAction = PNMessageAction( + type = "", + value = randomValue(), + messageTimetoken = 1L + ) + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_TYPE_MISSING, e) + } + } + + @Test + fun testAddAction_NoMessageActionValue() { + try { + pubnub.addMessageAction().apply { + channel = randomValue() + messageAction = PNMessageAction( + type = randomValue(), + value = "", + messageTimetoken = 1L + ) + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_VALUE_MISSING, e) + } + } + + @Test + fun testGetActions_NoChannel() { + try { + pubnub.getMessageActions() + .sync() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testRemoveAction_NoChannel() { + try { + pubnub.removeMessageAction() + .sync() + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testRemoveAction_NoMessageTimetoken() { + try { + pubnub.removeMessageAction().apply { + channel = randomValue() + actionTimetoken = 1L + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_TIMETOKEN_MISSING, e) + } + } + + @Test + fun testRemoveAction_NoMessageActionTimetoken() { + try { + pubnub.removeMessageAction().apply { + channel = randomValue() + messageTimetoken = 1L + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_ACTION_TIMETOKEN_MISSING, e) + } + } + + @Test + fun testAddSameActionTwice() { + val expectedChannel = randomValue() + val expectedEmoji = emoji() + + val timetoken = pubnub.publish().apply { + channel = expectedChannel + message = randomValue() + shouldStore = true + }.sync()!!.timetoken + + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "reaction", + value = expectedEmoji, + messageTimetoken = timetoken + ) + }.sync()!! + + pubnub.addMessageAction().apply { + channel = expectedChannel + messageAction = PNMessageAction( + type = "reaction", + value = expectedEmoji, + messageTimetoken = timetoken + ) + }.await { _, status -> + assertTrue(status.error) + assertEquals(409, status.statusCode) + } + + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PresenceEventsIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/PresenceEventsIntegrationTests.kt new file mode 100644 index 000000000..7ff22ef61 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PresenceEventsIntegrationTests.kt @@ -0,0 +1,119 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.listen +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import org.awaitility.Awaitility +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +class PresenceEventsIntegrationTests : BaseIntegrationTest() { + + lateinit var guest: PubNub + + override fun onBefore() { + guest = createPubNub() + } + + @Test + fun testJoinChannel() { + val success = AtomicBoolean() + val expectedChannel = randomValue() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + assertEquals("join", pnPresenceEventResult.event) + assertEquals(expectedChannel, pnPresenceEventResult.channel) + success.set(true) + } + + }) + + pubnub.subscribeToBlocking(expectedChannel) + + success.listen() + } + + @Test + fun testLeaveChannel() { + val success = AtomicBoolean() + val expectedChannel = randomValue() + + pubnub.subscribeToBlocking(expectedChannel) + guest.subscribeToBlocking(expectedChannel) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + assertEquals(expectedChannel, pnPresenceEventResult.channel) + assertEquals("leave", pnPresenceEventResult.event) + success.set(true) + } + }) + + guest.unsubscribeFromBlocking(expectedChannel) + + success.listen() + } + + @Test + fun testTimeoutFromChannel() { + val success = AtomicBoolean() + val expectedChannel = randomValue() + + pubnub.configuration.presenceTimeout = 20 + pubnub.configuration.heartbeatInterval = 0 + + pubnub.addListener(object : SubscribeCallback() { + + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (pnPresenceEventResult.event == "timeout") { + assertEquals(expectedChannel, pnPresenceEventResult.channel) + success.set(true) + } + } + + }) + + pubnub.subscribeToBlocking(expectedChannel) + + Awaitility.await() + .atMost(21, TimeUnit.SECONDS) + .untilTrue(success) + } + + @Test + fun testStateChangeEvent() { + val success = AtomicBoolean() + val expectedChannel = randomValue() + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.addListener(object : SubscribeCallback() { + + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + assertEquals("state-change", pnPresenceEventResult.event) + assertEquals(pubnub.configuration.uuid, pnPresenceEventResult.uuid) + success.set(true) + } + + }) + + pubnub.setPresenceState().apply { + channels = listOf(expectedChannel) + state = generatePayload() + }.sync()!! + + success.listen() + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PresenceIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/PresenceIntegrationTests.kt new file mode 100644 index 000000000..4537de842 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PresenceIntegrationTests.kt @@ -0,0 +1,252 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNHeartbeatNotificationOptions +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult +import com.pubnub.api.suite.await + +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.hamcrest.core.IsEqual +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class PresenceIntegrationTests : BaseIntegrationTest() { + + @Test + fun testWhereNow() { + val expectedChannelsCount = 4 + val expectedChannels = generateSequence { randomValue() }.take(expectedChannelsCount).toList() + + pubnub.subscribeToBlocking(*expectedChannels.toTypedArray()) + + pubnub.whereNow().await { result, status -> + assertFalse(status.error) + assertEquals(expectedChannelsCount, result!!.channels.size) + assertEquals(expectedChannels.sorted(), result.channels.sorted()) + } + } + + @Test + fun testGlobalHereNow() { + val expectedChannelsCount = 2 + val expectedClientsCount = 3 + + val expectedChannels = generateSequence { randomValue() }.take(expectedChannelsCount).toList() + + val clients = mutableListOf(pubnub).apply { + addAll(generateSequence { createPubNub() }.take(expectedClientsCount - 1).toList()) + } + + clients.forEach { + it.subscribeToBlocking(*expectedChannels.toTypedArray()) + } + + wait(TIMEOUT_MEDIUM) + + pubnub.hereNow().apply { + includeUUIDs = true + }.await { result, status -> + assertFalse(status.error) + assertTrue(result!!.totalOccupancy >= expectedClientsCount) + assertTrue(result.totalChannels >= expectedChannelsCount) + assertTrue(result.channels.size >= expectedChannelsCount) + + assertTrue(result.channels.keys.containsAll(expectedChannels)) + + result.channels.forEach { (key, value) -> + if (expectedChannels.contains(key)) { + assertTrue(value.occupancy >= expectedClientsCount) + assertTrue(value.occupants.size >= expectedClientsCount) + + assertEquals( + clients.map { it.configuration.uuid }.toList(), + value.occupants.map { it.uuid }.toList() + ) + } + } + } + } + + @Test + fun testHereNow() { + val expectedChannelsCount = 2 + val expectedClientsCount = 3 + + val expectedChannels = generateSequence { randomValue() }.take(expectedChannelsCount).toList() + + val clients = mutableListOf(pubnub).apply { + addAll(generateSequence { createPubNub() }.take(expectedClientsCount - 1).toList()) + } + + clients.forEach { + it.subscribeToBlocking(*expectedChannels.toTypedArray()) + } + + assertEquals(expectedChannelsCount, expectedChannels.size) + assertEquals(expectedClientsCount, clients.size) + + wait(TIMEOUT_MEDIUM) + + pubnub.hereNow().apply { + channels = expectedChannels + includeUUIDs = true + }.await { result, status -> + assertFalse(status.error) + assertEquals(expectedChannelsCount, result!!.totalChannels) + assertEquals(expectedChannelsCount, result.channels.size) + assertEquals(expectedChannelsCount * expectedClientsCount, result.totalOccupancy) + result.channels.forEach { (key, value) -> + assertTrue(expectedChannels.contains(key)) + assertTrue(expectedChannels.contains(value.channelName)) + assertEquals(expectedClientsCount, value.occupancy) + assertEquals(expectedClientsCount, value.occupants.size) + value.occupants.forEach { occupant -> + val uuid = occupant.uuid + var contains = false + for (client in clients) { + if (client.configuration.uuid == uuid) { + contains = true + break + } + } + assertTrue(contains) + } + } + } + } + + @Test + fun testPresenceState() { + val hits = AtomicInteger() + val expectedHits = 2 + val expectedStatePayload = generatePayload() + val expectedChannel = randomValue() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { + if (pnPresenceEventResult.event == "state-change" + && pnPresenceEventResult.channel == expectedChannel + && pnPresenceEventResult.uuid == pubnub.configuration.uuid + ) { + assertEquals(expectedStatePayload, pnPresenceEventResult.state) + hits.incrementAndGet() + } + } + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.setPresenceState().apply { + channels = listOf(expectedChannel) + state = expectedStatePayload + }.await { result, status -> + assertFalse(status.error) + assertEquals(expectedStatePayload, result!!.state) + } + + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .untilAtomic(hits, IsEqual.equalTo(1)) + + pubnub.getPresenceState().apply { + channels = listOf(expectedChannel) + }.await { result, status -> + assertFalse(status.error) + assertEquals(expectedStatePayload, result!!.stateByUUID[expectedChannel]) + hits.incrementAndGet() + } + + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .untilAtomic(hits, IsEqual.equalTo(expectedHits)) + } + + @Test + fun testHeartbeatsDisabled() { + val heartbeatCallsCount = AtomicInteger() + val subscribeSuccess = AtomicBoolean() + val expectedChannel = randomValue() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.configuration.heartbeatNotificationOptions) + + pubnub.configuration.presenceTimeout = 20; + pubnub.configuration.heartbeatInterval = 0; + + assertEquals(20, pubnub.configuration.presenceTimeout) + assertEquals(0, pubnub.configuration.heartbeatInterval) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (!pnStatus.error && pnStatus.affectedChannels.contains(expectedChannel)) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true) + } + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + heartbeatCallsCount.incrementAndGet() + } + } + } + }) + + pubnub.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute() + + Awaitility.await() + .atMost(20, TimeUnit.SECONDS) + .pollDelay(19, TimeUnit.SECONDS) + .until { + subscribeSuccess.get() && heartbeatCallsCount.get() == 0 + } + } + + @Test + fun testHeartbeatsEnabled() { + val heartbeatCallsCount = AtomicInteger() + val subscribeSuccess = AtomicBoolean() + val expectedChannel: String = randomValue() + + pubnub.configuration.heartbeatNotificationOptions = PNHeartbeatNotificationOptions.ALL + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.configuration.heartbeatNotificationOptions) + + pubnub.configuration.presenceTimeout = 20 + + assertEquals(20, pubnub.configuration.presenceTimeout) + assertEquals(9, pubnub.configuration.heartbeatInterval) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + println(pnStatus.operation) + if (!pnStatus.error && pnStatus.affectedChannels.contains(expectedChannel)) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true) + } + if (pnStatus.operation == PNOperationType.PNHeartbeatOperation) { + heartbeatCallsCount.incrementAndGet() + } + } + } + }) + + pubnub.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute() + + Awaitility.await() + .atMost(20, TimeUnit.SECONDS) + .until { + subscribeSuccess.get() && heartbeatCallsCount.get() > 2 + } + } +} diff --git a/src/test/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt new file mode 100644 index 000000000..f89f72f69 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt @@ -0,0 +1,373 @@ +package com.pubnub.api.integration + +import com.google.gson.JsonObject +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.listen +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import com.pubnub.api.suite.await +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.hamcrest.core.IsEqual +import org.json.JSONArray +import org.json.JSONObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class PublishIntegrationTests : BaseIntegrationTest() { + + @Test + fun testPublishMessage() { + val expectedChannel = randomValue() + + pubnub.publish().apply { + channel = expectedChannel + message = generatePayload() + }.await { _, status -> + assertFalse(status.error) + assertEquals(status.uuid, pubnub.configuration.uuid) + } + } + + @Test + fun testPublishMessageHistory() { + val expectedChannel = randomValue() + + val expectedPayload = JSONObject().apply { + put("name", "joe") + put("age", 48) + } + + val convertedPayload = pubnub.mapper.convertValue(expectedPayload, JsonObject::class.java) + + pubnub.publish().apply { + channel = expectedChannel + message = expectedPayload + }.sync()!! + + wait() + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannel) + maximumPerChannel = 1 + + }.await { result, status -> + assertFalse(status.error) + assertEquals(1, result!!.channels.size) + assertEquals(1, result.channels[expectedChannel]!!.size) + assertEquals(convertedPayload, result.channels[expectedChannel]!![0].message) + } + } + + @Test + fun testPublishMessageNoHistory() { + val expectedChannel = randomValue() + val messagePayload = generateMessage(pubnub) + + pubnub.publish().apply { + message = messagePayload + channel = expectedChannel + shouldStore = false + }.await { _, status -> + assertFalse(status.error) + assertEquals(status.uuid, pubnub.configuration.uuid) + } + + wait() + + pubnub.history().apply { + count = 1 + channel = expectedChannel + }.await { result, status -> + assertFalse(status.error) + assertEquals(0, result!!.messages.size) + } + } + + @Test + fun testReceiveMessage() { + val success = AtomicBoolean() + val expectedChannel = randomValue() + val messagePayload = generateMessage(pubnub) + + val observer = createPubNub() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation + && pnStatus.affectedChannels.contains(expectedChannel) + ) { + observer.publish().apply { + message = messagePayload + channel = expectedChannel + }.async { _, status -> + assertFalse(status.error) + } + } + } + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + assertEquals(expectedChannel, pnMessageResult.channel) + assertEquals(observer.configuration.uuid, pnMessageResult.publisher) + assertEquals(messagePayload, pnMessageResult.message) + success.set(true) + } + + }) + + pubnub.subscribeToBlocking(expectedChannel) + + success.listen() + } + + @Test + fun testOrgJsonObject_Get_History() { + val expectedChannel = randomValue() + + val expectedPayload = JSONObject().apply { + put("name", "John Doe") + put("city", "San Francisco") + } + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + usePost = true + }.sync()!! + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 1 + }.sync()!!.run { + assertEquals(expectedPayload.toString(), JSONObject(messages[0].entry.toString()).toString()) + } + } + + @Test + fun testOrgJsonObject_Post_History() { + val expectedChannel = randomValue() + val expectedPayload = generatePayloadJSON() + + pubnub.publish().apply { + channel = expectedChannel + message = expectedPayload + usePost = true + }.sync()!! + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 1 + }.sync()!!.run { + assertEquals(expectedPayload.toString(), JSONObject(messages[0].entry.toString()).toString()) + } + } + + @Test + fun testOrgJsonObject_Get_Receive() { + val expectedChannel = randomValue() + val expectedPayload = generatePayloadJSON() + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + success.set(expectedPayload.toString() == JSONObject(pnMessageResult.message.toString()).toString()) + } + + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + }.sync()!! + + wait() + + success.listen() + } + + @Test + fun testOrgJsonObject_Post_Receive() { + val expectedChannel = randomValue() + val expectedPayload = generatePayloadJSON() + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + success.set(expectedPayload.toString() == JSONObject(pnMessageResult.message.toString()).toString()) + } + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + usePost = true + }.sync()!! + + success.listen() + } + + @Test + fun testOrgJsonArray_Get_History() { + val expectedChannel = randomValue() + val expectedPayload = JSONArray().apply { + repeat(2) { put(generatePayloadJSON()) } + } + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + }.sync()!! + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 1 + }.sync()!!.run { + assertEquals(expectedPayload.toString(), JSONArray(messages[0].entry.toString()).toString()) + } + } + + @Test + fun testOrgJsonArray_Post_History() { + val expectedChannel = randomValue() + val expectedPayload = JSONArray().apply { + repeat(2) { put(generatePayloadJSON()) } + } + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + usePost = true + }.sync()!! + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 1 + }.sync()!!.run { + assertEquals(expectedPayload.toString(), JSONArray(messages[0].entry.toString()).toString()) + } + } + + @Test + fun testOrgJsonArray_Get_Receive() { + val expectedChannel = randomValue() + val expectedPayload = JSONArray().apply { + repeat(2) { put(generatePayloadJSON()) } + } + + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + success.set(expectedPayload.toString() == JSONArray(pnMessageResult.message.toString()).toString()) + } + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + }.sync() + + success.listen() + } + + @Test + fun testOrgJsonArray_Post_Receive() { + val expectedChannel = randomValue() + val expectedPayload = JSONArray().apply { + repeat(2) { put(generatePayloadJSON()) } + } + + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + success.set(expectedPayload.toString() == JSONArray(pnMessageResult.message.toString()).toString()) + } + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.publish().apply { + message = expectedPayload + channel = expectedChannel + usePost = true + }.sync()!! + + success.listen() + } + + @Test + fun testOrgJson_Combo() { + val expectedChannel = randomValue() + + val expectedPayload = JSONObject().apply { + put("key_1", generatePayloadJSON()) + put("key_2", generatePayloadJSON()) + } + expectedPayload.put("z_1", JSONObject(expectedPayload.toString())) + expectedPayload.put("a_2", JSONObject(expectedPayload.toString())) + expectedPayload.put("d_3", JSONObject(expectedPayload.toString())) + expectedPayload.put("z_array", JSONArray().apply { repeat(3) { put(generatePayloadJSON()) } }) + + val count = AtomicInteger() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + assertEquals( + expectedPayload.toString(), + JSONObject(pnMessageResult.message.toString()).toString() + ) + count.incrementAndGet() + } + }) + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.publish().apply { + channel = expectedChannel + message = expectedPayload + usePost = true + }.sync() + + wait() + + pubnub.fetchMessages().apply { + channels = listOf(expectedChannel) + maximumPerChannel = 1 + }.sync()!!.run { + assertEquals( + expectedPayload.toString(), + JSONObject(channels[expectedChannel]!![0].message.toString()).toString() + ) + count.incrementAndGet() + } + + Awaitility.await() + .atMost(Durations.ONE_MINUTE) + .untilAtomic(count, IsEqual.equalTo(2)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PushIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/PushIntegrationTest.kt new file mode 100644 index 000000000..69f61069b --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PushIntegrationTest.kt @@ -0,0 +1,111 @@ +package com.pubnub.api.integration + +import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.enums.PNPushType +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* + +class PushIntegrationTest : BaseIntegrationTest() { + + lateinit var expectedChannels: List + lateinit var expectedDeviceId: String + lateinit var expectedTopic: String + + override fun onBefore() { + expectedChannels = + generateSequence { UUID.randomUUID().toString().substring(0, 8).toUpperCase() }.take(3).toList() + expectedDeviceId = + generateSequence { (0..9).random() }.take(70).toList().shuffled().joinToString(separator = "") + expectedTopic = UUID.randomUUID().toString() + } + + @Test + fun testEnumNames() { + assertEquals("apns", PNPushType.APNS.toParamString()) + assertEquals("gcm", PNPushType.FCM.toParamString()) + assertEquals("mpns", PNPushType.MPNS.toParamString()) + assertEquals("apns2", PNPushType.APNS2.toParamString()) + } + + @Test + fun testCycle() { + PNPushType.values().forEach { + runPushOperations(it) + } + } + + private fun runPushOperations(pushType: PNPushType) { + // todo logger + println("Push type '$pushType'") + println("Channels $expectedChannels'") + println("Topic $expectedTopic'") + println("GeneratedToken '${expectedDeviceId.length}': $expectedDeviceId") + + pubnub.addPushNotificationsOnChannels().apply { + channels = expectedChannels + this.pushType = pushType + topic = expectedTopic + deviceId = expectedDeviceId + + }.sync()!! + + wait() + + pubnub.auditPushChannelProvisions().apply { + deviceId = expectedDeviceId + this.pushType = pushType + topic = expectedTopic + environment = PNPushEnvironment.DEVELOPMENT + + }.sync()!!.run { + assertTrue(channels.containsAll(expectedChannels)) + } + + wait() + + pubnub.removePushNotificationsFromChannels().apply { + this.pushType = pushType + environment = PNPushEnvironment.DEVELOPMENT + deviceId = expectedDeviceId + topic = expectedTopic + channels = listOf(expectedChannels[0]) + + }.sync()!! + + wait() + + pubnub.auditPushChannelProvisions().apply { + deviceId = expectedDeviceId + this.pushType = pushType + topic = expectedTopic + environment = PNPushEnvironment.DEVELOPMENT + }.sync()!!.apply { + assertFalse(channels.isEmpty()) + assertFalse(channels.contains(expectedChannels[0])) + } + + wait() + + pubnub.removeAllPushNotificationsFromDeviceWithPushToken().apply { + this.pushType = pushType + environment = PNPushEnvironment.DEVELOPMENT + deviceId = expectedDeviceId + topic = expectedTopic + + }.sync()!! + + wait() + + pubnub.auditPushChannelProvisions().apply { + deviceId = expectedDeviceId + this.pushType = pushType + topic = expectedTopic + environment = PNPushEnvironment.DEVELOPMENT + }.sync()!!.apply { + assertTrue(channels.isEmpty()) + assertFalse(channels.containsAll(expectedChannels)) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/PushPayloadHelperIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/PushPayloadHelperIntegrationTest.kt new file mode 100644 index 000000000..2d860bf39 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/PushPayloadHelperIntegrationTest.kt @@ -0,0 +1,63 @@ +package com.pubnub.api.integration + +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.models.consumer.PNStatus +import org.junit.jupiter.api.Test +import java.util.* + +class PushPayloadHelperIntegrationTest : BaseIntegrationTest() { + + @Test + fun testIntercept() { + val expectedChannel = UUID.randomUUID().toString() + + val payload = Gson().fromJson(json, JsonObject::class.java) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + pubnub.publish().apply { + channel = expectedChannel + message = payload + }.sync()!! + } + }) + + pubnub.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute() + + wait() + } + + val json = """ + { + "match": { + "tournament": "Barclay's Premier League", + "date": "2018-04-05 13:30:45", + "venue": "Anfield Road", + "title": "Goal! 90 min", + "summary": "Liverpool - Chelsea 2:1" + }, + "pn_gcm": { + "data": { + "title": "Goal! 90 min", + "summary": "Liverpool - Chelsea 2:1" + } + }, + "pn_apns": { + "aps": { + "title": "Goal! 90 min", + "summary": "Liverpool - Chelsea 2:1" + } + }, + "pn_mpns": { + "title": "Goal! 90 min", + "summary": "Liverpool - Chelsea 2:1" + } + } + """.trimIndent() +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/SignalIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/SignalIntegrationTests.kt new file mode 100644 index 000000000..a2656c256 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/SignalIntegrationTests.kt @@ -0,0 +1,121 @@ +package com.pubnub.api.integration + +import com.google.gson.Gson +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubError +import com.pubnub.api.assertPnException +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.listen +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNSignalResult +import com.pubnub.api.suite.await +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class SignalIntegrationTests : BaseIntegrationTest() { + + lateinit var expectedChannel: String + lateinit var expectedPayload: String + + override fun onBefore() { + expectedChannel = randomValue() + expectedPayload = randomValue(5) + } + + @Test + fun testPublishSignalMessageAsync() { + pubnub.signal().apply { + message = expectedPayload + channel = expectedChannel + }.await { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNSignalOperation, status.operation) + assertEquals(status.uuid, pubnub.configuration.uuid) + assertNotNull(result) + } + } + + @Test + fun testPublishSignalMessageSync() { + pubnub.signal().apply { + message = expectedPayload + channel = expectedChannel + }.sync()!! + } + + @Test + fun testReceiveSignalMessage() { + val success = AtomicBoolean() + + val observerClient = createPubNub() + + observerClient.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNSubscribeOperation + && pnStatus.affectedChannels.contains(expectedChannel) + ) { + pubnub.signal().apply { + message = expectedPayload + channel = expectedChannel + }.async { result, status -> + assertFalse(status.error) + assertEquals(PNOperationType.PNSignalOperation, status.operation) + assertEquals(status.uuid, pubnub.configuration.uuid) + assertNotNull(result) + } + } + } + + override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) { + assertEquals(pubnub.configuration.uuid, pnSignalResult.publisher) + assertEquals(expectedChannel, pnSignalResult.channel) + assertEquals(expectedPayload, Gson().fromJson(pnSignalResult.message, String::class.java)) + success.set(true) + } + }) + + observerClient.subscribe().apply { + channels = listOf(expectedChannel) + }.execute() + + success.listen() + } + + @Test + fun testPublishSignalMessageSyncWithoutChannel() { + try { + pubnub.signal().apply { + message = randomValue() + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.CHANNEL_MISSING, e) + } + } + + @Test + fun testPublishSignalMessageSyncWithoutMessage() { + try { + pubnub.signal().apply { + channel = randomValue() + }.sync()!! + } catch (e: Exception) { + assertPnException(PubNubError.MESSAGE_MISSING, e) + } + } + + @Test + fun testPublishSignalMessageSyncWithoutSubKey() { + try { + pubnub.configuration.subscribeKey = "" + pubnub.signal().apply { + channel = randomValue() + message = randomValue() + }.sync() + } catch (e: Exception) { + assertPnException(PubNubError.SUBSCRIBE_KEY_MISSING, e) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.kt new file mode 100644 index 000000000..6cb1d0acd --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.kt @@ -0,0 +1,132 @@ +package com.pubnub.api.integration + +import com.pubnub.api.suite.await +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class StorageAndPlaybackIntegrationTests : BaseIntegrationTest() { + + @Test + fun testHistoryMessages() { + val expectedMessage = randomValue() + val expectedChannel = randomValue() + + pubnub.publish().apply { + channel = expectedChannel + message = expectedMessage + }.await { _, _ -> } + + wait() + + pubnub.history().apply { + this.channel = expectedChannel + }.await { result, status -> + assertFalse(status.error) + assertEquals(expectedMessage, result!!.messages[0].entry.asString) + } + } + + @Test + fun testHistoryMessagesWithTimeToken() { + val expectedChannel = randomValue() + + repeat(3) { + pubnub.publish().apply { + channel = expectedChannel + message = randomValue() + }.sync()!! + } + + wait() + + pubnub.history().apply { + channel = expectedChannel + includeTimetoken = true + }.await { result, status -> + assertFalse(status.error) + result!!.messages.forEach { + assertNotNull(it.timetoken) + } + } + } + + @Test + fun testLoadingHistoryMessagesWithLimit() { + val expectedChannel = randomValue() + + repeat(20) { + pubnub.publish().apply { + channel = expectedChannel + message = randomValue() + }.sync()!! + } + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 10 + }.await { result, status -> + assertFalse(status.error) + assertEquals(10, result!!.messages.size) + } + } + + @Test + fun testLoadingHistoryWithSpecificTimeInterval() { + val expectedChannel = randomValue() + + val before = System.currentTimeMillis() * 10000 + wait(5) + repeat(3) { + pubnub.publish().apply { + channel = expectedChannel + message = randomValue() + }.sync()!! + } + wait(5) + val now = System.currentTimeMillis() * 10000 + + pubnub.history().apply { + channel = expectedChannel + includeTimetoken = true + start = now + end = before + count = 10 + }.await { result, status -> + assertFalse(status.error) + assertEquals(3, result!!.messages.size) + } + } + + @Test + fun testReverseHistoryPaging() { + val expectedChannel: String = randomValue() + val message1: String = randomValue(20) + val message2: String = randomValue(20) + + + pubnub.publish().apply { + channel = expectedChannel + message = message1 + }.sync()!! + + pubnub.publish().apply { + channel = expectedChannel + message = message2 + }.sync()!! + + wait() + + pubnub.history().apply { + channel = expectedChannel + count = 10 + reverse = true + }.await { result, status -> + assertFalse(status.error) + assertEquals(message1, result!!.messages[0].entry.asString) + assertEquals(message2, result.messages[1].entry.asString) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/SubscribeIntegrationTests.kt b/src/test/kotlin/com/pubnub/api/integration/SubscribeIntegrationTests.kt new file mode 100644 index 000000000..9d42f7989 --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/SubscribeIntegrationTests.kt @@ -0,0 +1,118 @@ +package com.pubnub.api.integration + +import com.pubnub.api.PubNub +import com.pubnub.api.callbacks.SubscribeCallback +import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.enums.PNStatusCategory +import com.pubnub.api.listen +import com.pubnub.api.models.consumer.PNStatus +import com.pubnub.api.models.consumer.pubsub.PNMessageResult +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class SubscribeIntegrationTests : BaseIntegrationTest() { + + lateinit var guestClient: PubNub + + override fun onBefore() { + guestClient = createPubNub() + } + + @Test + fun testSubscribeToMultipleChannels() { + val expectedChannelList = generateSequence { randomValue() }.take(3).toList() + + pubnub.subscribe().apply { + channels = expectedChannelList + withPresence = true + }.execute() + + wait() + + assertEquals(3, pubnub.getSubscribedChannels().size) + assertTrue(pubnub.getSubscribedChannels().contains(expectedChannelList[0])) + assertTrue(pubnub.getSubscribedChannels().contains(expectedChannelList[1])) + assertTrue(pubnub.getSubscribedChannels().contains(expectedChannelList[2])) + } + + @Test + fun testSubscribeToChannel() { + val expectedChannel = randomValue() + + pubnub.subscribe().apply { + channels = listOf(expectedChannel) + withPresence = true + }.execute() + + wait() + + assertEquals(1, pubnub.getSubscribedChannels().size) + assertTrue(pubnub.getSubscribedChannels().contains(expectedChannel)) + } + + @Test + fun testWildcardSubscribe() { + val success = AtomicBoolean() + + val expectedMessage = randomValue() + + pubnub.subscribeToBlocking("my.*") + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) {} + + override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { + assertEquals(expectedMessage, pnMessageResult.message.asString) + success.set(true) + } + }) + + guestClient.publish().apply { + channel = "my.test" + message = expectedMessage + }.sync()!! + + success.listen() + } + + @Test + fun testUnsubscribeFromChannel() { + val success = AtomicBoolean() + + val expectedChannel = randomValue() + + pubnub.subscribeToBlocking(expectedChannel) + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + if (pnStatus.operation == PNOperationType.PNUnsubscribeOperation + && pnStatus.category == PNStatusCategory.PNAcknowledgmentCategory + ) { + success.set(pubnub.getSubscribedChannels().none { it == expectedChannel }) + } + } + }) + + pubnub.unsubscribeFromBlocking(expectedChannel) + + success.listen() + } + + @Test + fun testUnsubscribeFromAllChannels() { + val success = AtomicBoolean() + + pubnub.addListener(object : SubscribeCallback() { + override fun status(pubnub: PubNub, pnStatus: PNStatus) { + success.set(pubnub.getSubscribedChannels().isEmpty()) + } + }) + + pubnub.unsubscribeAll() + + success.listen() + } + +} diff --git a/src/test/kotlin/com/pubnub/api/integration/TimeIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/TimeIntegrationTest.kt new file mode 100644 index 000000000..55a7530ca --- /dev/null +++ b/src/test/kotlin/com/pubnub/api/integration/TimeIntegrationTest.kt @@ -0,0 +1,25 @@ +package com.pubnub.api.integration + +import com.pubnub.api.listen +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +class TimeIntegrationTest : BaseIntegrationTest() { + + @Test + fun testGetPubNubTime() { + val success = AtomicBoolean() + + pubnub.time() + .async { result, status -> + assertFalse(status.error) + assertNotNull(result!!.timetoken) + success.set(true) + } + + success.listen() + } + +} \ No newline at end of file From eb7eee637fd35ada606e92b52460748dbc580c5e Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:32:35 +0200 Subject: [PATCH 96/98] Cleanup tests --- src/test/kotlin/com/pubnub/api/Utils.kt | 30 ++- .../com/pubnub/api/integration/AppTest.kt | 6 - .../api/integration/BaseIntegrationTest.kt | 229 +++++++++++++++++- .../integration/EncodingIntegrationTest.kt | 6 +- src/test/resources/junit-platform.properties | 1 + 5 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 src/test/resources/junit-platform.properties diff --git a/src/test/kotlin/com/pubnub/api/Utils.kt b/src/test/kotlin/com/pubnub/api/Utils.kt index 6b7b60a20..21465ed63 100644 --- a/src/test/kotlin/com/pubnub/api/Utils.kt +++ b/src/test/kotlin/com/pubnub/api/Utils.kt @@ -5,7 +5,6 @@ import com.github.tomakehurst.wiremock.verification.LoggedRequest import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.pubnub.api.models.consumer.PNStatus -import com.pubnub.api.suite.SpecialChar import okhttp3.HttpUrl import okhttp3.Request import org.awaitility.Awaitility @@ -14,21 +13,20 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import java.io.BufferedReader import java.io.InputStreamReader +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.stream.Collectors +var DEFAULT_LISTEN_DURATION: Int? = null -private fun observe(success: AtomicBoolean) { +private fun observe(success: AtomicBoolean, seconds: Int) { Awaitility.await() - .atMost(Durations.TWO_SECONDS) - .with() - .until(success::get) + .atMost(seconds.toLong(), TimeUnit.SECONDS) + .untilTrue(success) } -fun AtomicBoolean.listen(): AtomicBoolean { - this.set(false) - observe(this) - return this +fun AtomicBoolean.listen(seconds: Int = DEFAULT_LISTEN_DURATION!!) { + observe(this, seconds) } fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { @@ -41,12 +39,6 @@ fun AtomicBoolean.listen(function: () -> Boolean): AtomicBoolean { return this } -fun PNStatus.printQueryParams() { - this.clientRequest!!.url().queryParameterNames().map { - print("$it ${this.clientRequest?.url()?.queryParameterValues(it)?.first()} ") - } -} - fun assertPnException(expectedPubNubError: PubNubError, pnStatus: PNStatus) { Assertions.assertTrue(pnStatus.error) assertEquals(expectedPubNubError, pnStatus.exception!!.pubnubError) @@ -174,4 +166,10 @@ fun getResourceFileAsString(fileName: String?): String? { } } -fun Any.stringify() = Gson().toJson(this) \ No newline at end of file +fun Any.stringify() = Gson().toJson(this) + +class SpecialChar( + val name: String, + val regular: String, + val encoded: String +) \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/integration/AppTest.kt b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt index f074aab7a..8b0a3ff04 100644 --- a/src/test/kotlin/com/pubnub/api/integration/AppTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/AppTest.kt @@ -14,9 +14,6 @@ import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult -import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult -import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult -import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult import org.awaitility.Awaitility import org.awaitility.Durations import org.junit.jupiter.api.* @@ -117,9 +114,6 @@ class AppTest { override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) diff --git a/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt index ed6e743ce..d68ffa5ab 100644 --- a/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/BaseIntegrationTest.kt @@ -1,14 +1,22 @@ package com.pubnub.api.integration +import com.google.gson.JsonObject +import com.pubnub.api.DEFAULT_LISTEN_DURATION import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity +import com.pubnub.api.models.consumer.PNPublishResult +import org.awaitility.Awaitility +import org.awaitility.Durations +import org.json.JSONObject import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance import java.util.* import java.util.UUID +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit @TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class BaseIntegrationTest { @@ -38,10 +46,11 @@ abstract class BaseIntegrationTest { PAM_PUB_KEY = properties.getProperty("pam_pub_key") PAM_SUB_KEY = properties.getProperty("pam_sub_key") PAM_SEC_KEY = properties.getProperty("pam_sec_key") + DEFAULT_LISTEN_DURATION = 5 } @BeforeEach - open fun before(): Unit { + private fun before() { onPrePubnub() pubnub = createPubNub() if (needsServer()) { @@ -123,6 +132,38 @@ abstract class BaseIntegrationTest { return null } + fun wait(seconds: Int = 3) { + Thread.sleep((seconds * 1_000).toLong()) + /*Awaitility.await() + .atMost(seconds.toLong(), TimeUnit.SECONDS) + .until { true }*/ + } + + /*protected fun subscribeToChannel(vararg channels: String, pubnub: PubNub = this.pubnub) { + pubnub.subscribe().apply { + this.channels = listOf(*channels) + withPresence = true + + }.execute() + + wait(2) + }*/ + +} + +internal fun PubNub.subscribeToBlocking(vararg channels: String) { + this.subscribe().apply { + this.channels = listOf(*channels) + withPresence = true + }.execute() + Thread.sleep(2000) +} + +internal fun PubNub.unsubscribeFromBlocking(vararg channels: String) { + unsubscribe().apply { + this.channels = listOf(*channels) + }.execute(); + Thread.sleep(2000) } fun randomValue(length: Int = 10): String { @@ -135,4 +176,188 @@ fun randomValue(length: Int = 10): String { fun randomNumeric(length: Int = 10): String { return generateSequence { (0..9).random() }.take(length).toList().shuffled().joinToString(separator = "") -} \ No newline at end of file +} + +fun publishMixed(pubnub: PubNub, count: Int, channel: String): List { + val list = mutableListOf() + repeat(count) { + val sync = pubnub.publish().apply { + this.channel = channel + this.message = "${it}_msg" + val meta = when { + it % 2 == 0 -> generateMap() + it % 3 == 0 -> randomValue(4) + else -> null + } + meta?.let { this.meta = it } + + }.sync() + list.add(sync!!) + } + return list +} + +fun generateMap() = mapOf( + "text" to randomValue(8), + "uncd" to unicode(), + "info" to randomValue(8) +) + +fun generatePayload(): JsonObject { + return JsonObject().apply { + addProperty("text", randomValue()) + addProperty("uncd", unicode(8)) + addProperty("info", randomValue()) + } +} + +fun generateMessage(pubnub: PubNub): JsonObject { + return JsonObject().apply { + addProperty("publisher", pubnub.configuration.uuid) + addProperty("text", randomValue()) + addProperty("uncd", unicode(8)) + + } +} + +fun generatePayloadJSON(): JSONObject { + return JSONObject().apply { + put("text", randomValue()) + put("uncd", unicode(8)) + put("info", randomValue()) + } +} + +fun unicode(length: Int = 5): String { + val unicodeChars = "!?+-=" + return unicodeChars.toList().shuffled().take(length).joinToString(separator = "") +} + +fun emoji(): String { + return listOf( + "๐Ÿ˜€", + "๐Ÿ˜", + "๐Ÿ˜‚", + "๐Ÿคฃ", + "๐Ÿ˜ƒ", + "๐Ÿ˜„", + "๐Ÿ˜…", + "๐Ÿ˜†", + "๐Ÿ˜‰", + "๐Ÿ˜Š", + "๐Ÿ˜‹", + "๐Ÿ˜Ž", + "๐Ÿ˜", + "๐Ÿ˜˜", + "๐Ÿฅฐ", + "๐Ÿ˜—", + "๐Ÿ˜™", + "๐Ÿ˜š", + "โ˜บ๏ธ", + "๐Ÿ™‚", + "๐Ÿค—", + "๐Ÿคฉ", + "๐Ÿค”", + "๐Ÿคจ", + "๐Ÿ˜", + "๐Ÿ˜‘", + "๐Ÿ˜ถ", + "๐Ÿ™„", + "๐Ÿ˜", + "๐Ÿ˜ฃ", + "๐Ÿ˜ฅ", + "๐Ÿ˜ฎ", + "๐Ÿค", + "๐Ÿ˜ฏ", + "๐Ÿ˜ช", + "๐Ÿ˜ซ", + "๐Ÿ˜ด", + "๐Ÿ˜Œ", + "๐Ÿ˜›", + "๐Ÿ˜œ", + "๐Ÿ˜", + "๐Ÿคค", + "๐Ÿ˜’", + "๐Ÿ˜“", + "๐Ÿ˜”", + "๐Ÿ˜•", + "๐Ÿ™ƒ", + "๐Ÿค‘", + "๐Ÿ˜ฒ", + "โ˜น๏ธ", + "๐Ÿ™", + "๐Ÿ˜–", + "๐Ÿ˜ž", + "๐Ÿ˜Ÿ", + "๐Ÿ˜ค", + "๐Ÿ˜ข", + "๐Ÿ˜ญ", + "๐Ÿ˜ฆ", + "๐Ÿ˜ง", + "๐Ÿ˜จ", + "๐Ÿ˜ฉ", + "๐Ÿคฏ", + "๐Ÿ˜ฌ", + "๐Ÿ˜ฐ", + "๐Ÿ˜ฑ", + "๐Ÿฅต", + "๐Ÿฅถ", + "๐Ÿ˜ณ", + "๐Ÿคช", + "๐Ÿ˜ต", + "๐Ÿ˜ก", + "๐Ÿ˜ ", + "๐Ÿคฌ", + "๐Ÿ˜ท", + "๐Ÿค’", + "๐Ÿค•", + "๐Ÿคข", + "๐Ÿคฎ", + "๐Ÿคง", + "๐Ÿ˜‡", + "๐Ÿค ", + "๐Ÿคก", + "๐Ÿฅณ", + "๐Ÿฅด", + "๐Ÿฅบ", + "๐Ÿคฅ", + "๐Ÿคซ", + "๐Ÿคญ", + "๐Ÿง", + "๐Ÿค“", + "๐Ÿ˜ˆ", + "๐Ÿ‘ฟ", + "๐Ÿ‘น", + "๐Ÿ‘บ", + "๐Ÿ’€", + "๐Ÿ‘ป", + "๐Ÿ‘ฝ", + "๐Ÿค–", + "๐Ÿ’ฉ", + "๐Ÿ˜บ", + "๐Ÿ˜ธ", + "๐Ÿ˜น", + "๐Ÿ˜ป", + "๐Ÿ˜ผ", + "๐Ÿ˜ฝ", + "๐Ÿ™€", + "๐Ÿ˜ฟ", + "๐Ÿ˜พ" + ).shuffled().take(5).joinToString("") +} + +inline fun retry(crossinline block: () -> Unit) { + Awaitility.await() + .pollInterval(Durations.TWO_HUNDRED_MILLISECONDS) + .atLeast(Durations.FIVE_SECONDS) + .until { + val latch = CountDownLatch(1) + try { + block.invoke() + } catch (e: Exception) { + e.printStackTrace() + } + latch.countDown() + latch.await(1, TimeUnit.SECONDS) + } +} diff --git a/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt b/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt index 9de6d0cae..55435135a 100644 --- a/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt +++ b/src/test/kotlin/com/pubnub/api/integration/EncodingIntegrationTest.kt @@ -31,7 +31,6 @@ class EncodingIntegrationTest : BaseIntegrationTest() { server.publish().apply { channel = expectedChannel message = value - shouldStore = true meta = mapOf( keyName to value ) @@ -55,10 +54,10 @@ class EncodingIntegrationTest : BaseIntegrationTest() { .pollInterval(Durations.FIVE_HUNDRED_MILLISECONDS) .atMost(Durations.FIVE_SECONDS) .until { - val (messages, _, _) = server.history().apply { + val messages = server.history().apply { channel = expectedChannel includeMeta = true - }.sync()!! + }.sync()!!.messages assertEquals(1, messages.size) assertEquals(value, messages[0].meta!!.asJsonObject[keyName].asString) @@ -88,7 +87,6 @@ class EncodingIntegrationTest : BaseIntegrationTest() { message = expectedMessage meta = expectedMetadata queryParam = mapOf(propertyName to regular) - shouldStore = true usePost = false }.async { _, status -> assertFalse(status.error) diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..454391d99 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = false \ No newline at end of file From 0a4df8c1f41e970ce6211a2bddf2ddbb9c5746ac Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:32:53 +0200 Subject: [PATCH 97/98] Improve legacy mock tests --- .../kotlin/com/pubnub/api/legacy/BaseTest.kt | 31 ++-- .../com/pubnub/api/legacy/PubNubTest.kt | 2 +- .../api/legacy/endpoints/EndpointTest.kt | 36 ++++- .../endpoints/access/GrantEndpointTest.kt | 2 - .../ListAllChannelGroupEndpointTest.kt | 2 +- .../endpoints/history/HistoryEndpointTest.kt | 133 ++++++++++-------- .../endpoints/history/MessageCountTest.kt | 1 - .../AddMessageActionEndpointTest.kt | 2 +- .../message_actions/ReceiveMessageActions.kt | 28 +--- .../legacy/endpoints/presence/LeaveTest.kt | 2 +- .../presence/WhereNowEndpointTest.kt | 2 +- .../api/legacy/endpoints/pubsub/SignalTest.kt | 6 - .../push/PushPayloadHelperHelperTest.kt | 7 +- .../managers/SubscriptionManagerTest.kt | 2 +- 14 files changed, 133 insertions(+), 123 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt index 5d57c5d56..cda88f520 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/BaseTest.kt @@ -1,30 +1,41 @@ package com.pubnub.api.legacy import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig +import com.pubnub.api.DEFAULT_LISTEN_DURATION import com.pubnub.api.PNConfiguration import com.pubnub.api.PubNub import com.pubnub.api.enums.PNLogVerbosity +import okhttp3.HttpUrl import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach abstract class BaseTest { - protected val wireMockServer = WireMockServer(wireMockConfig()) - - protected var pubnub = PubNub(PNConfiguration().apply { - subscribeKey = "mySubscribeKey" - publishKey = "myPublishKey" - uuid = "myUUID" - origin = "localhost:8080" - secure = false - logVerbosity = PNLogVerbosity.BODY - }) + lateinit var wireMockServer: WireMockServer + protected lateinit var pubnub: PubNub @BeforeEach fun beforeEach() { + // wireMockServer = WireMockServer(wireMockConfig().dynamicPort()) + wireMockServer = WireMockServer( + wireMockConfig() + .bindAddress("localhost") + .dynamicPort() + ) wireMockServer.start() + WireMock.configureFor("http", "localhost", wireMockServer.port()) + pubnub = PubNub(PNConfiguration().apply { + subscribeKey = "mySubscribeKey" + publishKey = "myPublishKey" + uuid = "myUUID" + origin = HttpUrl.parse(wireMockServer.baseUrl())!!.run { "${host()}:${port()}" } + secure = false + logVerbosity = PNLogVerbosity.BODY + }) + DEFAULT_LISTEN_DURATION = 2 onBefore() } diff --git a/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt b/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt index 1d9b7468a..2c399fbfe 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/PubNubTest.kt @@ -78,7 +78,7 @@ class PubNubTest : BaseTest() { pubnub = PubNub(config) val version = pubnub.version val timeStamp = pubnub.timestamp() - assertEquals("0.0.2-dev", version) + assertEquals("4.0.0-dev", version) assertTrue(timeStamp > 0) } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt index 262d3446c..d0ae912ba 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/EndpointTest.kt @@ -4,12 +4,10 @@ import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.matching.UrlPattern import com.google.gson.JsonArray import com.google.gson.JsonObject -import com.pubnub.api.Endpoint +import com.pubnub.api.* import com.pubnub.api.enums.PNOperationType import com.pubnub.api.enums.PNStatusCategory import com.pubnub.api.legacy.BaseTest -import com.pubnub.api.listen -import com.pubnub.api.param import okhttp3.Request import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -250,6 +248,38 @@ class EndpointTest : BaseTest() { success.listen() } + @Test + fun testDefaultTimeoutValues() { + val p = PubNub(PNConfiguration()) + assertEquals(300, p.configuration.presenceTimeout) + assertEquals(0, p.configuration.heartbeatInterval) + } + + @Test + fun testCustomTimeoutValues1() { + val p = PubNub(PNConfiguration()) + p.configuration.presenceTimeout = 100 + assertEquals(100, p.configuration.presenceTimeout) + assertEquals(49, p.configuration.heartbeatInterval) + } + + @Test + fun testCustomTimeoutValues2() { + val p = PubNub(PNConfiguration()) + p.configuration.heartbeatInterval = 100 + assertEquals(300, p.configuration.presenceTimeout) + assertEquals(100, p.configuration.heartbeatInterval) + } + + @Test + fun testCustomTimeoutValues3() { + val p = PubNub(PNConfiguration()) + p.configuration.heartbeatInterval = 40 + p.configuration.presenceTimeout = 50 + assertEquals(50, p.configuration.presenceTimeout) + assertEquals(24, p.configuration.heartbeatInterval) + } + private fun fakeEndpoint( paramsCondition: (map: HashMap) -> Unit diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt index 84ea1e2da..eb32a32c7 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/access/GrantEndpointTest.kt @@ -1733,6 +1733,4 @@ class GrantEndpointTest : BaseTest() { Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic) } - // todo does telemetry param count? - } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt index 961d8ed80..de770c55c 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/channel_groups/ListAllChannelGroupEndpointTest.kt @@ -44,7 +44,7 @@ class ListAllChannelGroupEndpointTest : BaseTest() { ) val response = pubnub.listAllChannelGroups().sync()!! - assertThat(response.groups, Matchers.contains("a", "b"))//todo matchers + assertThat(response.groups, Matchers.contains("a", "b")) // todo matchers } @Test diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt index 4934645e5..d583267d3 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/HistoryEndpointTest.kt @@ -99,23 +99,25 @@ class HistoryEndpointTest : BaseTest() { .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) ) - val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + val result = pubnub.history().apply { channel = "niceChannel" includeTimetoken = true }.sync()!! - assertEquals(1234, startTimetoken) - assertEquals(4321, endTimetoken) + with(result) { + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) - assertEquals(2, messages.size) + assertEquals(2, messages.size) - assertEquals(1111, messages[0].timetoken) - assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) - assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) - assertEquals(2222, messages[1].timetoken) - assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) - assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } } @Test @@ -188,29 +190,31 @@ class HistoryEndpointTest : BaseTest() { ) ) - val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + val result = pubnub.history().apply { channel = "niceChannel" includeTimetoken = false }.sync()!! - assertEquals(14606134331557852, startTimetoken) - assertEquals(14606134485013970, endTimetoken) + with(result) { + assertEquals(14606134331557852, startTimetoken) + assertEquals(14606134485013970, endTimetoken) - assertEquals(3, messages.size) + assertEquals(3, messages.size) - assertNull(messages[0].timetoken) + assertNull(messages[0].timetoken) - assertEquals("m1", messages[0].entry.asJsonArray[0].asString) - assertEquals("m2", messages[0].entry.asJsonArray[1].asString) - assertEquals("m3", messages[0].entry.asJsonArray[2].asString) + assertEquals("m1", messages[0].entry.asJsonArray[0].asString) + assertEquals("m2", messages[0].entry.asJsonArray[1].asString) + assertEquals("m3", messages[0].entry.asJsonArray[2].asString) - assertEquals("m1", messages[1].entry.asJsonArray[0].asString) - assertEquals("m2", messages[1].entry.asJsonArray[1].asString) - assertEquals("m3", messages[1].entry.asJsonArray[2].asString) + assertEquals("m1", messages[1].entry.asJsonArray[0].asString) + assertEquals("m2", messages[1].entry.asJsonArray[1].asString) + assertEquals("m3", messages[1].entry.asJsonArray[2].asString) - assertEquals("m1", messages[2].entry.asJsonArray[0].asString) - assertEquals("m2", messages[2].entry.asJsonArray[1].asString) - assertEquals("m3", messages[2].entry.asJsonArray[2].asString) + assertEquals("m1", messages[2].entry.asJsonArray[0].asString) + assertEquals("m2", messages[2].entry.asJsonArray[1].asString) + assertEquals("m3", messages[2].entry.asJsonArray[2].asString) + } } @Test @@ -237,19 +241,20 @@ class HistoryEndpointTest : BaseTest() { ) - val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + val result = pubnub.history().apply { channel = "niceChannel" includeTimetoken = false }.sync()!! - assertEquals(14606134331557852, startTimetoken) - assertEquals(14606134485013970, endTimetoken) + with(result) { + assertEquals(14606134331557852, startTimetoken) + assertEquals(14606134485013970, endTimetoken) - assertEquals(1, messages.size) - - assertNull(messages[0].timetoken) - assertEquals("hey", messages[0].entry.asJsonObject.get("pn_other").asJsonObject.get("text").asString) + assertEquals(1, messages.size) + assertNull(messages[0].timetoken) + assertEquals("hey", messages[0].entry.asJsonObject.get("pn_other").asJsonObject.get("text").asString) + } } @Test @@ -277,22 +282,24 @@ class HistoryEndpointTest : BaseTest() { .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) ) - val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + val result = pubnub.history().apply { channel = "niceChannel" }.sync()!! - assertEquals(1234, startTimetoken) - assertEquals(4321, endTimetoken) + with(result) { + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) - assertEquals(2, messages.size) + assertEquals(2, messages.size) - assertNull(messages[0].timetoken) - assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) - assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + assertNull(messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) - assertNull(messages[1].timetoken) - assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) - assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + assertNull(messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } } @Test @@ -375,20 +382,20 @@ class HistoryEndpointTest : BaseTest() { assertEquals(PNOperationType.PNHistoryOperation, status.operation) - val (messages, startTimetoken, endTimetoken) = result!! + with(result!!) { + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) - assertEquals(1234, startTimetoken) - assertEquals(4321, endTimetoken) + assertEquals(2, messages.size) - assertEquals(2, messages.size) - - assertEquals(1111, messages[0].timetoken) - assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) - assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) - assertEquals(2222, messages[1].timetoken) - assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) - assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } success.set(true) } @@ -431,7 +438,7 @@ class HistoryEndpointTest : BaseTest() { .willReturn(aResponse().withBody(pubnub.mapper.toJson(testArray))) ) - val (messages, startTimetoken, endTimetoken) = pubnub.history().apply { + val result = pubnub.history().apply { channel = "niceChannel" count = 5 reverse = true @@ -455,18 +462,20 @@ class HistoryEndpointTest : BaseTest() { assertEquals("2", requests.first().queryParameter("end").firstValue()) assertEquals("true", requests.first().queryParameter("include_token").firstValue()) - assertEquals(1234, startTimetoken) - assertEquals(4321, endTimetoken) + with(result) { + assertEquals(1234, startTimetoken) + assertEquals(4321, endTimetoken) - assertEquals(2, messages.size) + assertEquals(2, messages.size) - assertEquals(1111, messages[0].timetoken) - assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) - assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) + assertEquals(1111, messages[0].timetoken) + assertEquals(11, messages[0].entry.asJsonObject["a"].asInt) + assertEquals(22, messages[0].entry.asJsonObject["b"].asInt) - assertEquals(2222, messages[1].timetoken) - assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) - assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + assertEquals(2222, messages[1].timetoken) + assertEquals(33, messages[1].entry.asJsonObject["a"].asInt) + assertEquals(44, messages[1].entry.asJsonObject["b"].asInt) + } } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt index 88d986c52..7d243913d 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/history/MessageCountTest.kt @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Test class MessageCountTest : BaseTest() { @Test - @Throws(PubNubException::class) fun testSingleChannelWithSingleToken() { stubFor( get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt index b2641895d..62ccdf504 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/AddMessageActionEndpointTest.kt @@ -45,7 +45,7 @@ class AddMessageActionEndpointTest : BaseTest() { assertEquals(result.messageTimetoken, 123) assertEquals(result.type, "emoji") - assertEquals(result.uuid, "someUuid") // todo what's the uuid? author? + assertEquals(result.uuid, "someUuid") assertEquals(result.value, "smiley") assertEquals(result.actionTimetoken, 1000) } diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt index 0990911b3..1c59c6978 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/message_actions/ReceiveMessageActions.kt @@ -11,9 +11,6 @@ import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult -import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult -import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult -import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.util.concurrent.atomic.AtomicBoolean @@ -83,18 +80,6 @@ class ReceiveMessageActions : BaseTest() { failTest() } - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) { - failTest() - } - - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) { - failTest() - } - - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) { - failTest() - } - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) { assertEquals(pnMessageActionResult.channel, "coolChannel") assertEquals(pnMessageActionResult.messageAction.messageTimetoken, 500) @@ -190,6 +175,7 @@ class ReceiveMessageActions : BaseTest() { override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) { failTest() + pnMessageResult.message } override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { @@ -200,18 +186,6 @@ class ReceiveMessageActions : BaseTest() { failTest() } - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) { - failTest() - } - - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) { - failTest() - } - - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) { - failTest() - } - override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) { count.incrementAndGet() if (count.get() == 2) { diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt index d1bd1b1b3..ca05735cf 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/LeaveTest.kt @@ -117,7 +117,7 @@ class LeaveTest : BaseTest() { Leave(pubnub).apply { channels = listOf("coolChannel", "coolChannel2") channelGroups = listOf("cg1") - }.async { result, status -> + }.async { _, status -> assertEquals(status.affectedChannels[0], "coolChannel") assertEquals(status.affectedChannels[1], "coolChannel2") assertEquals(status.affectedChannelGroups[0], "cg1") diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt index 81a962046..82dae9571 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/presence/WhereNowEndpointTest.kt @@ -41,7 +41,7 @@ class WhereNowEndpointTest : BaseTest() { ) val response = pubnub.whereNow().sync()!! - assertThat(response.channels, Matchers.contains("a", "b")) // todo compare timestamp param like this + assertThat(response.channels, Matchers.contains("a", "b")) } @Test diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt index a5865e21a..b2ad1a906 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/pubsub/SignalTest.kt @@ -10,9 +10,6 @@ import com.pubnub.api.models.consumer.pubsub.PNMessageResult import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult import com.pubnub.api.models.consumer.pubsub.PNSignalResult import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult -import com.pubnub.api.models.consumer.pubsub.objects.PNMembershipResult -import com.pubnub.api.models.consumer.pubsub.objects.PNSpaceResult -import com.pubnub.api.models.consumer.pubsub.objects.PNUserResult import okhttp3.HttpUrl import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -144,9 +141,6 @@ class SignalTest : BaseTest() { override fun status(pubnub: PubNub, pnStatus: PNStatus) {} override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {} override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {} - override fun user(pubnub: PubNub, pnUserResult: PNUserResult) {} - override fun space(pubnub: PubNub, pnSpaceResult: PNSpaceResult) {} - override fun membership(pubnub: PubNub, pnMembershipResult: PNMembershipResult) {} override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {} }) diff --git a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt index cc81e6f3c..2732bb117 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/endpoints/push/PushPayloadHelperHelperTest.kt @@ -1,8 +1,7 @@ package com.pubnub.api.legacy.endpoints.push -import com.google.gson.Gson -import com.pubnub.api.legacy.BaseTest import com.pubnub.api.enums.PNPushEnvironment +import com.pubnub.api.legacy.BaseTest import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.* import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper.APNSPayload.APNS2Configuration @@ -175,8 +174,6 @@ class PushPayloadHelperHelperTest : BaseTest() { val map = pushPayloadHelper.build() - println(Gson().toJson(map)) - val apnsMap = map["pn_apns"] as Map<*, *> val pnPushList = apnsMap["pn_push"] as List> @@ -276,8 +273,6 @@ class PushPayloadHelperHelperTest : BaseTest() { val map = pushPayloadHelper.build() - print(map) - val pnFcmMap = map["pn_gcm"] as Map<*, *> assertNotNull(pnFcmMap) diff --git a/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt index 1e561f701..e86fb845c 100644 --- a/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt +++ b/src/test/kotlin/com/pubnub/api/legacy/managers/SubscriptionManagerTest.kt @@ -2260,7 +2260,7 @@ class SubscriptionManagerTest : BaseTest() { override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) { if (pnPresenceEventResult.event == "state-change") { if (pnPresenceEventResult.state!!.asJsonObject.has("state") && - pnPresenceEventResult.state.asJsonObject.get("state").asString == "cool" + pnPresenceEventResult.state!!.asJsonObject.get("state").asString == "cool" ) { atomic.set(true) } From 565102053845196f40bc8ca1832e900cd884d9af Mon Sep 17 00:00:00 2001 From: azurqsd Date: Thu, 11 Jun 2020 19:33:21 +0200 Subject: [PATCH 98/98] Improve test suite tests --- .../com/pubnub/api/suite/EndpointTestSuite.kt | 25 ++++++++----------- .../RemoveChannelChannelGroupTestSuite.kt | 20 ++++++++++++--- .../v3/FetchMessagesMetaActionsTestSuite.kt | 2 +- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt index 444930640..46364be42 100644 --- a/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/EndpointTestSuite.kt @@ -17,7 +17,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean -typealias AsyncCheck = (pnStatus: PNStatus, result: T?) -> Unit +typealias AsyncCheck = (status: PNStatus, result: T?) -> Unit abstract class EndpointTestSuite, R> : BaseTest() { @@ -63,7 +63,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { lateinit var telemetryParamName: String - snippet().eval { _, status -> + snippet().await { _, status -> assertFalse(status.error) assertEquals(pnOperation(), status.operation) assertEquals(PNStatusCategory.PNAcknowledgmentCategory, status.category) @@ -90,7 +90,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { @Test fun testSuccessAsync() { - snippet().eval { result, status -> + snippet().await { result, status -> // todo // status.exception?.printStackTrace() assertFalse(status.error) @@ -139,7 +139,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { unsuccessfulResponseBodyList().forEach { val stub = stubFor(mappingBuilder().willReturn(aResponse().withBody(it))) - snippet().eval { _, status -> + snippet().await { _, status -> assertTrue(status.error) assertPnException(PubNubError.PARSING_ERROR, status) } @@ -176,7 +176,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { assertPnException(PubNubError.PARSING_ERROR, e) } - snippet().eval { result, status -> + snippet().await { result, status -> assertEquals(!voidResponse(), status.error) if (!voidResponse()) { @@ -225,7 +225,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { optionalScenarioList().forEach { val stub = stubFor(mappingBuilder().willReturn(it.build())) - snippet().eval { result, status -> + snippet().await { result, status -> it.additionalChecks.invoke(status, result) if (it.result == Result.SUCCESS) { assertFalse(status.error) @@ -249,7 +249,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { queryParam = getSpecialCharsMap().map { it.name to it.regular }.toMap() - }.eval { result, status -> + }.await { result, status -> assertFalse(status.error) assertNotNull(result) @@ -308,7 +308,7 @@ abstract class EndpointTestSuite, R> : BaseTest() { private fun testAuthKeyAsync() { pubnub.configuration.authKey = "someAuthKey" - snippet().eval { result, status -> + snippet().await { _, status -> assertFalse(status.error) if (requiredKeys().contains(AUTH)) { @@ -379,7 +379,7 @@ private fun Int.contains(sub: Int): Boolean { return extractKeys(this).contains(sub) } -private fun Endpoint.eval(function: (result: Output?, status: PNStatus) -> Unit) { +fun Endpoint.await(function: (result: Output?, status: PNStatus) -> Unit) { val success = AtomicBoolean() async { result, status -> function.invoke(result, status) @@ -391,7 +391,7 @@ private fun Endpoint.eval(function: (result: Outp class OptionalScenario { var responseBuilder: ResponseDefinitionBuilder.() -> ResponseDefinitionBuilder = { this } - var additionalChecks: AsyncCheck = { status: PNStatus, result: R? -> } + var additionalChecks: AsyncCheck = { _: PNStatus, _: R? -> } var result: Result = Result.SUCCESS var pnError: PubNubError? = null @@ -405,8 +405,3 @@ enum class Result { FAIL } -class SpecialChar( - val name: String, - val regular: String, - val encoded: String -) \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt index 355030273..571acc927 100644 --- a/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/channel_groups/RemoveChannelChannelGroupTestSuite.kt @@ -4,10 +4,11 @@ import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock.* import com.pubnub.api.endpoints.channel_groups.RemoveChannelChannelGroup import com.pubnub.api.enums.PNOperationType +import com.pubnub.api.models.consumer.PNStatus import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult -import com.pubnub.api.suite.AUTH -import com.pubnub.api.suite.EndpointTestSuite -import com.pubnub.api.suite.SUB +import com.pubnub.api.suite.* +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue class RemoveChannelChannelGroupTestSuite : EndpointTestSuite() { @@ -40,4 +41,17 @@ class RemoveChannelChannelGroupTestSuite : override fun voidResponse() = true + override fun optionalScenarioList(): List> { + return listOf( + OptionalScenario().apply { + responseBuilder = { withBody("").withStatus(400) } + result = Result.FAIL + additionalChecks = { pnStatus: PNStatus, result: PNChannelGroupsRemoveChannelResult? -> + assertTrue(pnStatus.error) + assertNull(result) + } + } + ) + } + } \ No newline at end of file diff --git a/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt index 9d50e9856..04edc00e7 100644 --- a/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt +++ b/src/test/kotlin/com/pubnub/api/suite/history/v3/FetchMessagesMetaActionsTestSuite.kt @@ -101,7 +101,7 @@ class FetchMessagesMetaActionsTestSuite : EndpointTestSuite