From a473a9d3fb2e6a8ed7ffbdb7f37ab5c91b0d3188 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 5 Jan 2022 17:44:02 +0100 Subject: [PATCH 01/36] new: initial api and integration tests. --- .gitignore | 2 + config/Migrations/schema-dump-default.lock | Bin 42139 -> 56757 bytes phpunit.xml.dist | 18 +-- src/Model/Table/AuditLogsTable.php | 4 +- tests/Fixture/AuthKeysFixture.php | 36 +++++ tests/Fixture/IndividualsFixture.php | 25 ++++ tests/Fixture/RolesFixture.php | 24 ++++ tests/Fixture/UsersFixture.php | 38 ++++++ tests/README.md | 48 +++++++ tests/TestCase/Api/UsersApiTest.php | 40 ++++++ tests/TestCase/ApplicationTest.php | 6 +- .../Controller/PagesControllerTest.php | 126 ------------------ .../Controller/UsersControllerTest.php | 32 +++++ tests/bootstrap.php | 17 +++ 14 files changed, 278 insertions(+), 138 deletions(-) create mode 100644 tests/Fixture/AuthKeysFixture.php create mode 100644 tests/Fixture/IndividualsFixture.php create mode 100644 tests/Fixture/RolesFixture.php create mode 100644 tests/Fixture/UsersFixture.php create mode 100644 tests/README.md create mode 100644 tests/TestCase/Api/UsersApiTest.php delete mode 100644 tests/TestCase/Controller/PagesControllerTest.php create mode 100644 tests/TestCase/Controller/UsersControllerTest.php diff --git a/.gitignore b/.gitignore index f84928d..869728c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ vendor webroot/theme/node_modules .vscode docker/run/ +.phpunit.result.cache +config.json \ No newline at end of file diff --git a/config/Migrations/schema-dump-default.lock b/config/Migrations/schema-dump-default.lock index 291060ccbeef6d3415e60d985954fd26141e1c02..8c9385cea7539531476901ab307ae28e67e054d4 100644 GIT binary patch literal 56757 zcmeHQOLOBm4&I-V%5hKK>GaEe+S$3xVRsKZ$Ek8DwvwpYmb{YOkLsHLz8@%w0zuFZ z%8sQT-x5Or(%VmdXZYoH^T?uDZPz*aPiF7v zO^lH(>A#Qk-~H^i{=P9?yhlS`r%!1~9lo30uG0Lee!l(mZ~XgVcDv0hOg6>453|?! z39shc)r$Uog@5t-GJQ<8tGamu|1XL)(MH{YRx`~vpm|#tzvYWEZQ7d7&_5CUyO|3O z2m&`{y2z@m$f?6WK@ncg@@4iVTW*up{Bl(6ooq8#L1;t6+u3bVJ|%fpB{fK!CKXm{ z5(vf*M~WafRb6KJlMte8_WGlc8v2(@jS;$ax_)RR&XnU6k@YC9~Mjzy7Uqb683 z!vsSr7`rL6by9w(C6?6l)@Jwgrp40Vniq9+(2PEMn>WuM9zW0TtT?gt#&eHFnZn@g z;&p&-vaDtLn3ie2uuI%857)n-9hwPR#F_VYLtCFlhBTEaDYS(#V_3&!x=KTBoyF|e zSfTfIJiWdFqy^nUE%t41G0#dXO{CQl#fB_fzqPL(oPDRa8e%ep^3(PQOo=fHM8x7OHNZ8T&5b5H|5c zmYaR&m@{b4ci<>gcjpY8j^VVqvCF!NHI9l8IZ5x-S^k|N5N2-`?}gkIHsOQ4m%S3w zYNKw#jfZkVgluK^oUr&cSkqCEEE?79GaVcb;QYHo*?UeRfDC}6Q+TJxO#QC}zX7_D(I3i7-~6HwnI?Zssl&a|2deLj%9t z^wKL5P4|dL`kgHroPFcXUW%m=woVNBx;u2S1z--NiKF2i%S+!^!YucBio64i!%SB1 zJ{V}v&9d~#4j9K_ITkK3R?pWcYWmj${cB4bHImj*U`$6Oq6Tw%2#mtFT+|@E19c4i zNYMhtZHM1&ME#s@JB{9Ip%-@T;2#pKo0b?n@(dWx7^X9D8W_3B_ex7!ACO7L!* z95o~C)=6P7M7ni|Gz1U^kMHk}h5_g-sTNr_e@gSTL}FlISePOWoikU}NeQ6hW0)`n z_m?LCo3$e#%}sFoa3AFI)92doGFhsY`)$d^Tm7#oYjU<|7`kejAb*{k1icXVHNTng#`L2}HA%w|=Ubb_Y zQrhg2F-*>Q!hrYNE>qK~H>;mxaIvrFF{%o~C2yio9$~0%-bO74M&q208i;og7(=dD z)Ht$}z*v0sqsHMKsAJ&A06cz~td`w_K6wx)WUJx9k*iA!qomC2>nd@ejSx-;gPWXL zd=5!4vvCg9R2du&vIP6GMnn|wSF-HVJAH!8YwBV>t%FPJhD||k%?mpg;OIkH6w9Gr z1?#7$g|G3%GeuT369_LnP5j?P;mx0oyW!}}bNUn*JWr?^!#C|%hdzjee>y=^s!oD# z&N%~%Fg4rq$Y8WrFLRNZ(*|9p)uPNyR##&_3B@?$xq%Xk>7m-X+*W})B`Dd4qFAMg zsgz){FScSZpniN<+HC%fsr~cy!bRpoW~}<0ZRVS@_-2Y%q`JDuv@mFk__Tc!R$BKW z4;5UyUT2}RlSV->_0iP`G{M*j6vyq%&{&)5*idWSgx}R!4)L?6mK)pLs$o^{DV~>A zE2fR95zMC9-?Bk&G}+E2UQC%wl$V)umR7Vfn80%EWaDXd5YwZ+G9^2ci7C)jB65T?@5lmQCi3k^lPYQD~x?c;`U(ft_*4j zjtCs5=L73dreBKBdL;{5nX!K%p8nl;Y@iaLVgcE`ybi>@_=@!6 zbc?c@=|>dBNoV9DVF(D%)g57p68O6mQ5IU*94l#!HcKS|QXqEM8a3$#K!2E=S=!;dP+D9z2*%lTekl| zu~2~Oxnc0SS`>81c_fL0x0vapR>jDO^sjMBHNqlx!A6jHxKE} zCM|IoZk;aiU|lGBUnLv6ET(+_jQIN=R_h-()dt79AG5_xGdmEtD(HCF6Y+x>&|(zN zx~WuRaf5~Cj^)BXW6KI*N=2E0^wCJB4l7i~V|$*bQ0OJ#JSK0!l&=)Xc#qSq^TNJU znmpKwW3`DU>^jG@+5O$e`;YJ5-+g=wu#1QGo-ykaBG9qgQBniF?AqIlDGsqCBsACi z0=oLT5bHud2uCy$-(zJJAEdY20`4=*CR%!HAR3se6)jwk$$4=#;dnXTK6F~GpgX^E z@evsg^DoR^o7xhs4D7P}hdG4Ov&cu;Xxo>g=wDY|2*ar!Lqud)*h&RP=vOS3g4X;8 z`ezFtH2`}*FalrKs3CX<;W3!8jaUebVLMRAz>fiU@#o0fQ1l~*-!?RDBN(|OTLa1d zY3t`m|3SI5S1}E(qVsxFePVNl;hLE_NoEGJiF?JeX;!4cNqNjl+*Ht|{trLhaz^L& z2-gW`(N@WX=MS*FDXJ`VHGmH04=qQ#BKQV1wzu+u*Drnz8DRlGi@^1-1j<1G-)b3w zxyfqsEi9Vd{UI71;X5b5Hn@i?$_UuizpjEV@L6yo3P8$E0%N&sjT*`ALLG;M2V^S+ zK-atjU1bUOK!ml>1aS`OXX0G#-*SZwxhfVo-ddyD_Y{cB=?z1-JDz1B_>RBKK}IDV zfB1c&P)ZX<2)LgwPZxE8+gSY|AH330pLc+SS80vAKj=a>fBK_F2*}G8)v&smV2Q-h z6)B_>urVil0)xjk{Idd=05>AmHjYWP%WPDCf5$42yO6+9i z-VDX+!#n|$Z`!E12wVzQi&jQ63u_vXz%+LR=(2BP1SrZb>t}D(aak0LgBX|9afmz} z2mRc4wve=}(>j^s4&&9}1D0NIDp28PjdkKoN6d#>>vws|B|feXt|HD#y)rG=hOe3r2wgSw@X@Hgtq%cD-B8^gwKpD(#U)lQ@j)&w7xUeEyZi6IP>lM5JWPl<} zdn&KB%1OM|?|jiZ#mj7@sZEU)iLdMH5h{!vU`z)wad_0Y{%9_#dg(< z+l>f_+P@y|-|F_b{@d3B{p%tT#V@_9z_`1FENb*!9jbG{j{#h6PLx8cOt>x@w86(? z@O$Vd7WS~0^g!TC3;4*9W{nUJ+WEl+3K&FQCleq({;r&$WbYuG4oEmg_>2UTD7R7D z7rs9yytsqDz7X!Vr`R0n z>R%7^Pn+^w2MURun83x7?mDB}FHTy*jR+j@SuZslLnMaL=FzYVavT3)FPh6`fR{$+ zAjqY4*i*;AmUE}kWvrzV#67$k0NZ1-L3Zq*30E@E)T>Rhm}H=u_W6{v?_Y3(tNFT~ zj}oC~M_oh$sUJ^{fS6Tm%fUh{0U`yfDdu55gNPjh-3!yCm4~&@9UreYFOCu0HWq!P zQT~SA*YkeoIPcfnV&RHdLnnSmSf5!D(demY$3em7vN}+_Xcdf;Enz!dJddvIHZtO2 zGakMq%XZr1oiK?)n3ysda}$2g##oP~MMMFQg>fQk0QP=h1iRi*{oA(#y}MEqHH6zm zU>q)DQKRq<)G_emMg2RwQ+)EU$dkRyg&YcTq2e%S%I^;g*LsTFm*N*PP0*KO;vge< zw#n$uuJ&$EO5TE?7;s~?!1NF<#AW!k@E1XpB-FY2#t}Z}UYi@Y2jl@RLIPQ*Qxw== zWbzRDE|@qqzQesi?QD&E1*Ug4z(P2`90_E-_(45^9LF&!!YZxFV;Kv?Zhstq2ccTx z-f2yN&OMbR+Sz^=Ep{`B-yKPnPK6bPQ=xfg$090z?At1StY#h4d;kY8!lYnqMcit; z&dt_Dp?Wr>^11HiK;ItcqXzMM35>?pF=`z4eqaPE4^jQwx7FV9;}ywc34Y7-;@4kD z=HOyA(JvFG1bYJUFX9{oQ9mAu9*b=~{9Z=ZPp`%}ayN%Jrdx9Y;T%s)mB%jzpcq-; zYrTo1uM&8J<##S-TxB_~(7ySa)y>_ENMuyWlL^y8N_UrTG~2(wK4}~jLI8-u_U^@j z^kD%fd-)<_F7`&Gle*wRf8c26wo!Q6b>tLH>;kjC+e;ScdVe9-w|@Mvez>0_KD8*8 z>Cg{DgRR&53yqOJQ8))05?Zvs_iohw3pe~GxS!=U>hEwo-^m_W18dF*wDEP}SHQ}2 zWDv@mD8}?51?=fvQgGt(xF5jU}#z zB97pn4s2z!H+Kz|KH`>lynp@d?aL>g2bY!R&o$F3A0NyiLasG^&A##+L z?7`{=;bJ)(&+&$AKL$Lkm$_W?jX8m%LiV9Pk#0|<~MIX!@2Mh6k`i zhn{36yKm-(($?WzJNX2eI%V}iTnQNz3vFn=nG?Pi*B7m`3;P(Mv7U{IFnDy=7R6~qqlF?xxmDb>}H4{_oTQEbNX z2#IfmmeadZRbHd<6nTpoR+C(H)Ucv8Iv)Is%yNN~O}VAcg2ug3*DyHG%eXCMBOPTn z)Pd1DRCOu*v>&$0`0C4ivZZh_$lKzL3HmZ7wYW-?DtX942UI}cwM+^w*on)eR1I~R zy|~xEgc5evB z?0X08>w@Z`cl)XvhPY^wJdY{9>ad{PLo(|<6YNm#Z=Esg0vI2lIQ~Lku`hNIicLm( z;*9hWqc7v05$;Xs!(4-6TDF&-4wh3gTW-?Fq)t#jBtz%c8#`zHkY^-)?~RsjoaaMe zf+0k>XG^=)mO`Ypnm~K!)(suNce3@>BFpAaX`Yt&s^TIWfNa@bW+@H62PD?8rc4x+ zc&q0CAjTf&JvRP0QVs(nVR*fXgtZz3^nvs)t;Z4`mr1`QV4q^2oOzR1^cZIdB{^&wJ%Yw zd51hEQD>$$ggg$KP8tc3p1d`WM+YmhTf$+St82eESQBvVxxP8nfc2Zfv0Pk;WLIdj zYjgq^+L&EyU(euZn)%x(Lev199jb9NV_2@lj$u0pi~;S&PhUo)&72<8QSc)qFB4Xs1w;c$b+Cy-;IiRz}z3#d>*P19us^u<91j+4||n1!U-JPpyfWN zV~go;lB)Vzl;#T^5^06KL$AE@BGZT3pjzH(R>Tmzf@hh1$(CC<%ac5r2l-iM$On(@ z3op{t%X}Tp8{Z_Yv$eT*hB?9nn5kN^Ocz;m$vgc*&}X|^-F|v7cXvVVj#eMA*%*~W zgKa6<{E7YrVCmdu}uh*5LjVmHKlCsa`BhEH5 zjVw=wzAffF3d%Pik*5}nl5q!TtYcxWE@j5FYXp$xT){YOm)T1o6y`($bETiqh=4~P zPvXYZdQwLvCgWiRum2QjGL?lb delta 3761 zcmb_fdrVVj6i=ars!U!*KweVRK|$%Il*esAamEFV=gaN)I=^$i z^ZT9C&aTi8`$7^7G*w79d1xx1RvPMTR(rkK?$vt@FmyRzqrwkkomr`Sa^31-XDM2# z(rtK7d{vJ1`&{hjKeK(!%Yw;x&(rA&#WJmg4gIL_R?m`}apcnlBEP=3I&A9MkjEMoTxrV0sGdytWux za#O(m$tLFH5}gDwx#$My$sG%z!#IMMQa}Nv#yK0c2*+a^u0wU6oWsOC$U6`^2NlGz z&2Dz1G(yy2HH^${9*=bD#%%bdTRs6S9eRqk%?fM=i7HkUq9zF9U#?0R+#v%wZ9xS5 z__ft2=3>y$JzIm#wAE0jhecG`GG0jYgv)08W_P30YjfE3wdTgjI6qMcNXkHou?YeD8}M zdfL4cx=s|47J-4{&St@H11Y33%Wfv?&69uBZ`B@O)u{Gp*~33I7C{T^du zMinu7Y(qcHKI{i)bT)H+(~1y|fUvXvGQ4K&C%p-7OWl|Wzi#%jgxlxLh4)RWI58C{ zgoj5>pnoNa(*dT+oE5B2Z8cSRWpu3y#jCXTPoiwITQi~B`2|iyy4G@Nv>7Yh_I2=yIdvG*lLzzN@a7-0o_ z)Z~KY2c^uZ?RzBw@j8DfO{yi*EC^eTBB}`0$Za*)Z5{(lbRr35d`Sr2Af(Q8qA+sy zl33PE-5Zh`Bc=$VX@QL(7;@ra`S*GS_K89rA=x>tPGF$1a;0f7CIa_`-T@JY!E*_a zrrwv1Bzem2|4kDG9NM)4p$zTHz%jU5R5V+qVwHg_SxHd2-y#~D0wHBc*@BZxDw!Jx zG{Mwii}a^-7VpW?OShwrwA)<&&1?L%*!cGa_=E`x%hyREs%aIBM6ZF;bzz|S+>fY* zt-ltAy0L;{mFFsMb!#cfSKP)cw|QW=W(*KbG-nCkQ(qR%=ix%!8WtSA#7^pH7C+_S?8gKn<+s`JJ)wM+d3qS)GKLT>paI1z{%J zCvEvEbK;G-HimO>eos4bFiPNHke0!t1x6@m8jz+kczmup2n6wUS8p-S_VgyAiVXKk zLh;Ali#{ByE_p?^UmB#7d7;6l-0CsAC!Z2ZYx|+%vpL)gGL1Lh3ms3g*{y6JCiA*& zb}OvBb@WO2LA^#8k`6Nk)Gi&Ix)TXMSrqW~u#^1DLTIEj+Ws8n=kxssYtD)Z diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7107122..7be6529 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,17 +15,17 @@ tests/TestCase/ - + + ./tests/TestCase/Controller + + + ./tests/TestCase/Api + - - - - - - - - + + + diff --git a/src/Model/Table/AuditLogsTable.php b/src/Model/Table/AuditLogsTable.php index efd6339..7987af6 100644 --- a/src/Model/Table/AuditLogsTable.php +++ b/src/Model/Table/AuditLogsTable.php @@ -163,8 +163,8 @@ class AuditLogsTable extends AppTable if ($this->user !== null) { return $this->user; } - - $this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT]; + + $this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT, 'name' => '']; $isShell = (php_sapi_name() === 'cli'); if ($isShell) { diff --git a/tests/Fixture/AuthKeysFixture.php b/tests/Fixture/AuthKeysFixture.php new file mode 100644 index 0000000..5924c5b --- /dev/null +++ b/tests/Fixture/AuthKeysFixture.php @@ -0,0 +1,36 @@ +records = [ + [ + 'id' => 1, + 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5', + 'authkey' => $hasher->hash(self::ADMIN_API_KEY), + 'authkey_start' => '4cd6', + 'authkey_end' => '4c2f', + 'expiration' => 0, + 'user_id' => 1, + 'comment' => '', + 'created' => time(), + 'modified' => time() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php new file mode 100644 index 0000000..31261f2 --- /dev/null +++ b/tests/Fixture/IndividualsFixture.php @@ -0,0 +1,25 @@ + 1, + 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e1', + 'email' => 'admin@admin.test', + 'first_name' => 'admin', + 'last_name' => 'admin', + 'position' => 'admin', + 'created' => '2022-01-04 10:00:00', + 'modified' => '2022-01-04 10:00:00' + ] + ]; +} diff --git a/tests/Fixture/RolesFixture.php b/tests/Fixture/RolesFixture.php new file mode 100644 index 0000000..ced82a1 --- /dev/null +++ b/tests/Fixture/RolesFixture.php @@ -0,0 +1,24 @@ + 1, + 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e4', + 'name' => 'admin', + 'is_default' => true, + 'perm_admin' => true, + 'perm_sync' => true, + 'perm_org_admin' => true + ] + ]; +} diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php new file mode 100644 index 0000000..df6b8bd --- /dev/null +++ b/tests/Fixture/UsersFixture.php @@ -0,0 +1,38 @@ +records = [ + [ + 'id' => 1, + 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5', + 'username' => self::ADMIN_USER, + 'password' => $hasher->hash(self::ADMIN_PASSWORD), + 'role_id' => 1, + 'individual_id' => 1, + 'disabled' => 0, + 'organisation_id' => 1, + 'created' => '2022-01-04 10:00:00', + 'modified' => '2022-01-04 10:00:00' + ] + ]; + parent::init(); + } +} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..898e249 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,48 @@ +# Testing +Add a test database to your `config/app_local.php` config file and set `debug` mode to `true`. +```php +'debug' => true, +'Datasources' => [ + 'default' => [ + ... + ], + /* + * The test connection is used during the test suite. + */ + 'test' => [ + 'host' => 'localhost', + 'username' => 'cerebrate', + 'password' => 'cerebrate', + 'database' => 'cerebrate_test', + ], +], +``` + +## Runing the tests + +``` +$ composer install +$ vendor/bin/phpunit +PHPUnit 8.5.22 by Sebastian Bergmann and contributors. + +..... 5 / 5 (100%) + +Time: 11.61 seconds, Memory: 26.00 MB + +OK (5 tests, 15 assertions) +``` + +Running a specific suite: +``` +$ vendor/bin/phpunit --testsuite=api +``` +Available suites: +* `app`: runs all test suites +* `api`: runs only api tests +* `controller`: runs only controller tests +* _to be continued ..._ + +By default the database is re-generated before running the test suite, to skip this step and speed up the test run use the `-d skip-migrations` option: +``` +$ vendor/bin/phpunit -d skip-migrations +``` diff --git a/tests/TestCase/Api/UsersApiTest.php b/tests/TestCase/Api/UsersApiTest.php new file mode 100644 index 0000000..5353305 --- /dev/null +++ b/tests/TestCase/Api/UsersApiTest.php @@ -0,0 +1,40 @@ +configRequest([ + 'headers' => [ + // this does not work: https://book.cakephp.org/4/en/development/testing.html#testing-stateless-authentication-and-apis + // 'Authorization' => AuthKeysFixture::ADMIN_API_KEY, + 'Accept' => 'application/json' + ] + ]); + + $this->get('/users/view'); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::ADMIN_USER)); + } +} diff --git a/tests/TestCase/ApplicationTest.php b/tests/TestCase/ApplicationTest.php index cd09f1e..e2d3183 100644 --- a/tests/TestCase/ApplicationTest.php +++ b/tests/TestCase/ApplicationTest.php @@ -40,10 +40,14 @@ class ApplicationTest extends IntegrationTestCase $app->bootstrap(); $plugins = $app->getPlugins(); - $this->assertCount(3, $plugins); + $this->assertCount(7, $plugins); $this->assertSame('Bake', $plugins->get('Bake')->getName()); $this->assertSame('DebugKit', $plugins->get('DebugKit')->getName()); $this->assertSame('Migrations', $plugins->get('Migrations')->getName()); + $this->assertSame('Authentication', $plugins->get('Authentication')->getName()); + $this->assertSame('ADmad/SocialAuth', $plugins->get('ADmad/SocialAuth')->getName()); + $this->assertSame('Tags', $plugins->get('Tags')->getName()); + $this->assertSame('Cake/TwigView', $plugins->get('Cake/TwigView')->getName()); } /** diff --git a/tests/TestCase/Controller/PagesControllerTest.php b/tests/TestCase/Controller/PagesControllerTest.php deleted file mode 100644 index f2958f9..0000000 --- a/tests/TestCase/Controller/PagesControllerTest.php +++ /dev/null @@ -1,126 +0,0 @@ -get('/'); - $this->assertResponseOk(); - $this->get('/'); - $this->assertResponseOk(); - } - - /** - * testDisplay method - * - * @return void - */ - public function testDisplay() - { - $this->get('/pages/home'); - $this->assertResponseOk(); - $this->assertResponseContains('CakePHP'); - $this->assertResponseContains(''); - } - - /** - * Test that missing template renders 404 page in production - * - * @return void - */ - public function testMissingTemplate() - { - Configure::write('debug', false); - $this->get('/pages/not_existing'); - - $this->assertResponseError(); - $this->assertResponseContains('Error'); - } - - /** - * Test that missing template in debug mode renders missing_template error page - * - * @return void - */ - public function testMissingTemplateInDebug() - { - Configure::write('debug', true); - $this->get('/pages/not_existing'); - - $this->assertResponseFailure(); - $this->assertResponseContains('Missing Template'); - $this->assertResponseContains('Stacktrace'); - $this->assertResponseContains('not_existing.php'); - } - - /** - * Test directory traversal protection - * - * @return void - */ - public function testDirectoryTraversalProtection() - { - $this->get('/pages/../Layout/ajax'); - $this->assertResponseCode(403); - $this->assertResponseContains('Forbidden'); - } - - /** - * Test that CSRF protection is applied to page rendering. - * - * @reutrn void - */ - public function testCsrfAppliedError() - { - $this->post('/pages/home', ['hello' => 'world']); - - $this->assertResponseCode(403); - $this->assertResponseContains('CSRF'); - } - - /** - * Test that CSRF protection is applied to page rendering. - * - * @reutrn void - */ - public function testCsrfAppliedOk() - { - $this->enableCsrfToken(); - $this->post('/pages/home', ['hello' => 'world']); - - $this->assertResponseCode(200); - $this->assertResponseContains('CakePHP'); - } -} diff --git a/tests/TestCase/Controller/UsersControllerTest.php b/tests/TestCase/Controller/UsersControllerTest.php new file mode 100644 index 0000000..0c856e8 --- /dev/null +++ b/tests/TestCase/Controller/UsersControllerTest.php @@ -0,0 +1,32 @@ +enableCsrfToken(); + $this->enableSecurityToken(); + + $this->post('/users/login', [ + 'username' => UsersFixture::ADMIN_USER, + 'password' => UsersFixture::ADMIN_PASSWORD, + ]); + + $this->assertSessionHasKey('authUser.uuid'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 962815c..f8088be 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@ ['*']`, prevents migrations from droping already created tables + (new SchemaLoader())->loadSqlFiles('./INSTALL/mysql.sql', 'test'); + $migrator = new Migrator(); + $migrator->runMany([ + ['connection' => 'test', 'skip' => ['*']], + ['plugin' => 'Tags', 'connection' => 'test', 'skip' => ['*']], + ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test', 'skip' => ['*']] + ]); +} From f45727704f319e6ae30e32a311266e3de8c5db08 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 5 Jan 2022 17:44:24 +0100 Subject: [PATCH 02/36] fix: deprecation warning --- src/Controller/Component/ParamHandlerComponent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Component/ParamHandlerComponent.php b/src/Controller/Component/ParamHandlerComponent.php index f76a578..89d6cce 100644 --- a/src/Controller/Component/ParamHandlerComponent.php +++ b/src/Controller/Component/ParamHandlerComponent.php @@ -47,7 +47,7 @@ class ParamHandlerComponent extends Component return $this->isRest; } if ($this->request->is('json')) { - if (!empty($this->request->input()) && empty($this->request->input('json_decode'))) { + if (!empty($this->request->getBody()) && !empty($this->request->getParsedBody())) { throw new MethodNotAllowedException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.'); } $this->isRest = true; From a69608530c9a31953df898a9a04643cf69e10069 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 7 Jan 2022 13:45:52 +0100 Subject: [PATCH 03/36] new: add /api openapi spec view with redoc, add faker to fixtures, validate api responses with openapi spec, add /api/v1/ prefix to api routes --- .gitignore | 2 +- composer.json | 3 +- config/routes.php | 22 +- src/Controller/ApiController.php | 19 ++ src/Controller/Component/ACLComponent.php | 3 + templates/Api/index.php | 2 + tests/Fixture/AuthKeysFixture.php | 52 ++++- tests/Fixture/IndividualsFixture.php | 72 +++++- tests/Fixture/OrganisationsFixture.php | 52 +++++ tests/Fixture/RolesFixture.php | 60 ++++- tests/Fixture/UsersFixture.php | 78 ++++++- tests/Helper/ApiTestTrait.php | 79 +++++++ tests/README.md | 11 +- tests/TestCase/Api/Users/UsersApiTest.php | 55 +++++ tests/TestCase/Api/UsersApiTest.php | 40 ---- .../{ => Users}/UsersControllerTest.php | 2 +- tests/bootstrap.php | 2 + webroot/docs/openapi.yaml | 215 ++++++++++++++++++ 18 files changed, 671 insertions(+), 98 deletions(-) create mode 100644 src/Controller/ApiController.php create mode 100644 templates/Api/index.php create mode 100644 tests/Fixture/OrganisationsFixture.php create mode 100644 tests/Helper/ApiTestTrait.php create mode 100644 tests/TestCase/Api/Users/UsersApiTest.php delete mode 100644 tests/TestCase/Api/UsersApiTest.php rename tests/TestCase/Controller/{ => Users}/UsersControllerTest.php (93%) create mode 100644 webroot/docs/openapi.yaml diff --git a/.gitignore b/.gitignore index 869728c..ba05ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ webroot/theme/node_modules .vscode docker/run/ .phpunit.result.cache -config.json \ No newline at end of file +config.json diff --git a/composer.json b/composer.json index bc562a0..4e1af85 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,9 @@ "cakephp/bake": "^2.0.3", "cakephp/cakephp-codesniffer": "~4.0.0", "cakephp/debug_kit": "^4.0", + "fzaninotto/faker": "^1.9", "josegonzalez/dotenv": "^3.2", + "league/openapi-psr7-validator": "^0.16.4", "phpunit/phpunit": "^8.5", "psy/psysh": "@stable" }, @@ -44,7 +46,6 @@ "scripts": { "post-install-cmd": "App\\Console\\Installer::postInstall", "post-create-project-cmd": "App\\Console\\Installer::postInstall", - "post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump", "check": [ "@test", "@cs-check" diff --git a/config/routes.php b/config/routes.php index ac1ab26..e467725 100644 --- a/config/routes.php +++ b/config/routes.php @@ -92,14 +92,14 @@ $routes->prefix('Open', function (RouteBuilder $routes) { $routes->fallbacks(DashedRoute::class); }); -/* - * If you need a different set of middleware or none at all, - * open new scope and define routes there. - * - * ``` - * $routes->scope('/api', function (RouteBuilder $builder) { - * // No $builder->applyMiddleware() here. - * // Connect API actions here. - * }); - * ``` - */ +// API routes +$routes->scope('/api', function (RouteBuilder $routes) { + // $routes->applyMiddleware('ratelimit', 'auth.api'); + $routes->scope('/v1', function (RouteBuilder $routes) { + // $routes->applyMiddleware('v1compat'); + $routes->setExtensions(['json']); + + // Generic API route + $routes->connect('/{controller}/{action}/*'); + }); +}); \ No newline at end of file diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php new file mode 100644 index 0000000..65cd11c --- /dev/null +++ b/src/Controller/ApiController.php @@ -0,0 +1,19 @@ +set('url', $url); + } +} diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index afa70ff..0c6d897 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -193,6 +193,9 @@ class ACLComponent extends Component 'getBookmarks' => ['*'], 'saveBookmark' => ['*'], 'deleteBookmark' => ['*'] + ], + 'Api' => [ + 'index' => ['*'] ] ); diff --git a/templates/Api/index.php b/templates/Api/index.php new file mode 100644 index 0000000..96be4b8 --- /dev/null +++ b/templates/Api/index.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/Fixture/AuthKeysFixture.php b/tests/Fixture/AuthKeysFixture.php index 5924c5b..8291811 100644 --- a/tests/Fixture/AuthKeysFixture.php +++ b/tests/Fixture/AuthKeysFixture.php @@ -11,24 +11,60 @@ class AuthKeysFixture extends TestFixture { public $connection = 'test'; - public const ADMIN_API_KEY = '4cd687b314a3b9c4d83264e6195b9a3706ef4c2f'; + public const ADMIN_API_KEY = 'd033e22ae348aeb5660fc2140aec35850c4da997'; + public const SYNC_API_KEY = '6b387ced110858dcbcda36edb044dc18f91a0894'; + public const ORG_ADMIN_API_KEY = '1c4685d281d478dbcebd494158024bc3539004d0'; + public const USER_API_KEY = '12dea96fec20593566ab75692c9949596833adc9'; public function init(): void { $hasher = new DefaultPasswordHasher(); + $faker = \Faker\Factory::create(); $this->records = [ [ - 'id' => 1, - 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5', + 'uuid' => $faker->uuid(), 'authkey' => $hasher->hash(self::ADMIN_API_KEY), - 'authkey_start' => '4cd6', - 'authkey_end' => '4c2f', + 'authkey_start' => substr(self::ADMIN_API_KEY, 0, 4), + 'authkey_end' => substr(self::ADMIN_API_KEY, -4), 'expiration' => 0, - 'user_id' => 1, + 'user_id' => UsersFixture::USER_ADMIN_ID, 'comment' => '', - 'created' => time(), - 'modified' => time() + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::SYNC_API_KEY), + 'authkey_start' => substr(self::SYNC_API_KEY, 0, 4), + 'authkey_end' => substr(self::SYNC_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_SYNC_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::ORG_ADMIN_API_KEY), + 'authkey_start' => substr(self::ORG_ADMIN_API_KEY, 0, 4), + 'authkey_end' => substr(self::ORG_ADMIN_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ORG_ADMIN_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::USER_API_KEY), + 'authkey_start' => substr(self::USER_API_KEY, 0, 4), + 'authkey_end' => substr(self::USER_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_REGULAR_USER_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() ] ]; parent::init(); diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php index 31261f2..9c96f8b 100644 --- a/tests/Fixture/IndividualsFixture.php +++ b/tests/Fixture/IndividualsFixture.php @@ -10,16 +10,64 @@ class IndividualsFixture extends TestFixture { public $connection = 'test'; - public $records = [ - [ - 'id' => 1, - 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e1', - 'email' => 'admin@admin.test', - 'first_name' => 'admin', - 'last_name' => 'admin', - 'position' => 'admin', - 'created' => '2022-01-04 10:00:00', - 'modified' => '2022-01-04 10:00:00' - ] - ]; + // Admin individual + public const INDIVIDUAL_ADMIN_ID = 1; + + // Sync individual + public const INDIVIDUAL_SYNC_ID = 2; + + // Org Admin individual + public const INDIVIDUAL_ORG_ADMIN_ID = 3; + + // Regular User individual + public const INDIVIDUAL_REGULAR_USER_ID = 4; + + public function init(): void + { + $faker = \Faker\Factory::create(); + + $this->records = [ + [ + 'id' => self::INDIVIDUAL_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'admin', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_SYNC_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'sync', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'org_admin', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'user', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } } diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php new file mode 100644 index 0000000..d8b81f9 --- /dev/null +++ b/tests/Fixture/OrganisationsFixture.php @@ -0,0 +1,52 @@ +records = [ + [ + 'id' => self::ORGANISATION_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Organisation A', + 'url' => $faker->url, + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::ORGANISATION_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Organisation B', + 'url' => $faker->url, + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/RolesFixture.php b/tests/Fixture/RolesFixture.php index ced82a1..1230e8f 100644 --- a/tests/Fixture/RolesFixture.php +++ b/tests/Fixture/RolesFixture.php @@ -10,15 +10,53 @@ class RolesFixture extends TestFixture { public $connection = 'test'; - public $records = [ - [ - 'id' => 1, - 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e4', - 'name' => 'admin', - 'is_default' => true, - 'perm_admin' => true, - 'perm_sync' => true, - 'perm_org_admin' => true - ] - ]; + public const ROLE_ADMIN_ID = 1; + public const ROLE_SYNC_ID = 2; + public const ROLE_ORG_ADMIN_ID = 3; + public const ROLE_REGULAR_USER_ID = 4; + + public function init(): void + { + $faker = \Faker\Factory::create(); + + $this->records = [ + [ + 'id' => self::ROLE_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'name' => 'admin', + 'is_default' => false, + 'perm_admin' => true, + 'perm_sync' => false, + 'perm_org_admin' => false + ], + [ + 'id' => self::ROLE_SYNC_ID, + 'uuid' => $faker->uuid(), + 'name' => 'sync', + 'is_default' => false, + 'perm_admin' => false, + 'perm_sync' => true, + 'perm_org_admin' => false + ], + [ + 'id' => self::ROLE_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'name' => 'org_admin', + 'is_default' => false, + 'perm_admin' => false, + 'perm_sync' => false, + 'perm_org_admin' => true + ], + [ + 'id' => self::ROLE_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'name' => 'user', + 'is_default' => true, + 'perm_admin' => false, + 'perm_sync' => false, + 'perm_org_admin' => false + ] + ]; + parent::init(); + } } diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php index df6b8bd..ba35e7a 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -11,26 +11,80 @@ class UsersFixture extends TestFixture { public $connection = 'test'; - public const ADMIN_USER = 'admin'; - public const ADMIN_PASSWORD = 'Password1234'; + // Admin user + public const USER_ADMIN_ID = 1; + public const USER_ADMIN_USERNAME = 'admin'; + public const USER_ADMIN_PASSWORD = 'AdminPassword'; + + // Sync user + public const USER_SYNC_ID = 2; + public const USER_SYNC_USERNAME = 'sync'; + public const USER_SYNC_PASSWORD = 'SyncPassword'; + + // Org Admin user + public const USER_ORG_ADMIN_ID = 3; + public const USER_ORG_ADMIN_USERNAME = 'org_admin'; + public const USER_ORG_ADMIN_PASSWORD = 'OrgAdminPassword'; + + // Regular User user + public const USER_REGULAR_USER_ID = 4; + public const USER_REGULAR_USER_USERNAME = 'user'; + public const USER_REGULAR_USER_PASSWORD = 'UserPassword'; + public function init(): void { $hasher = new DefaultPasswordHasher(); - + $faker = \Faker\Factory::create(); $this->records = [ [ - 'id' => 1, - 'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5', - 'username' => self::ADMIN_USER, - 'password' => $hasher->hash(self::ADMIN_PASSWORD), - 'role_id' => 1, - 'individual_id' => 1, + 'id' => self::USER_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_ADMIN_USERNAME, + 'password' => $hasher->hash(self::USER_ADMIN_PASSWORD), + 'role_id' => RolesFixture::ROLE_ADMIN_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, 'disabled' => 0, - 'organisation_id' => 1, - 'created' => '2022-01-04 10:00:00', - 'modified' => '2022-01-04 10:00:00' + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_SYNC_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_SYNC_USERNAME, + 'password' => $hasher->hash(self::USER_SYNC_PASSWORD), + 'role_id' => RolesFixture::ROLE_SYNC_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_SYNC_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_ORG_ADMIN_USERNAME, + 'password' => $hasher->hash(self::USER_ORG_ADMIN_PASSWORD), + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_ORG_ADMIN_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_REGULAR_USER_USERNAME, + 'password' => $hasher->hash(self::USER_REGULAR_USER_PASSWORD), + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() ] ]; parent::init(); diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php new file mode 100644 index 0000000..e749677 --- /dev/null +++ b/tests/Helper/ApiTestTrait.php @@ -0,0 +1,79 @@ +_authToken = $authToken; + + // somehow this is not set automatically in test environment + $_SERVER['HTTP_AUTHORIZATION'] = $authToken; + + $this->configRequest([ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => $this->_authToken + ] + ]); + } + + /** + * Parse the OpenAPI specification and create a validator + * + * @param string $specFile + * @return void + */ + public function initializeValidator(string $specFile): void + { + $this->validator = (new ValidatorBuilder)->fromYamlFile($specFile); + $this->requestValidator = $this->validator->getRequestValidator(); + $this->responseValidator = $this->validator->getResponseValidator(); + } + + /** + * Validates the API request against the OpenAPI spec + * + * @param string $path The path to the API endpoint + * @param string $method The HTTP method used to call the endpoint + * @return void + */ + public function validateRequest(string $endpoint, string $method = 'get'): void + { + // TODO: find a workaround to create a PSR-7 request object for validation + throw NotImplementedException("Unfortunately cakephp does not save the PSR-7 request object in the test context"); + } + + /** + * Validates the API response against the OpenAPI spec + * + * @param string $path The path to the API endpoint + * @param string $method The HTTP method used to call the endpoint + * @return void + */ + public function validateResponse(string $endpoint, string $method = 'get'): void + { + $address = new OperationAddress($endpoint, $method); + $this->responseValidator->validate($address, $this->_response); + } +} diff --git a/tests/README.md b/tests/README.md index 898e249..f37bcf3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,5 +1,14 @@ # Testing -Add a test database to your `config/app_local.php` config file and set `debug` mode to `true`. + +1. Add a `cerebrate_test` database to the db: +```mysql +CREATE DATABASE cerebrate_test; +GRANT ALL PRIVILEGES ON cerebrate_test.* to cerebrate@localhost; +FLUSH PRIVILEGES; +QUIT; +``` + +2. Add a the test database to your `config/app_local.php` config file and set `debug` mode to `true`. ```php 'debug' => true, 'Datasources' => [ diff --git a/tests/TestCase/Api/Users/UsersApiTest.php b/tests/TestCase/Api/Users/UsersApiTest.php new file mode 100644 index 0000000..2f2c2b8 --- /dev/null +++ b/tests/TestCase/Api/Users/UsersApiTest.php @@ -0,0 +1,55 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewMe(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + // TODO: $this->validateRequest() + $this->validateResponse(self::ENDPOINT); + } + + public function testViewById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ADMIN_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + // TODO: $this->validateRequest() + $this->validateResponse($url); + } +} diff --git a/tests/TestCase/Api/UsersApiTest.php b/tests/TestCase/Api/UsersApiTest.php deleted file mode 100644 index 5353305..0000000 --- a/tests/TestCase/Api/UsersApiTest.php +++ /dev/null @@ -1,40 +0,0 @@ -configRequest([ - 'headers' => [ - // this does not work: https://book.cakephp.org/4/en/development/testing.html#testing-stateless-authentication-and-apis - // 'Authorization' => AuthKeysFixture::ADMIN_API_KEY, - 'Accept' => 'application/json' - ] - ]); - - $this->get('/users/view'); - - $this->assertResponseOk(); - $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::ADMIN_USER)); - } -} diff --git a/tests/TestCase/Controller/UsersControllerTest.php b/tests/TestCase/Controller/Users/UsersControllerTest.php similarity index 93% rename from tests/TestCase/Controller/UsersControllerTest.php rename to tests/TestCase/Controller/Users/UsersControllerTest.php index 0c856e8..37dd73a 100644 --- a/tests/TestCase/Controller/UsersControllerTest.php +++ b/tests/TestCase/Controller/Users/UsersControllerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Test\TestCase\Controller; +namespace App\Test\TestCase\Controller\Users; use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f8088be..9283095 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -66,4 +66,6 @@ if (!in_array('skip-migrations', $_SERVER['argv'])) { ['plugin' => 'Tags', 'connection' => 'test', 'skip' => ['*']], ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test', 'skip' => ['*']] ]); +}else{ + echo "[ * ] Skipping migrations ...\n"; } diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml new file mode 100644 index 0000000..5a55225 --- /dev/null +++ b/webroot/docs/openapi.yaml @@ -0,0 +1,215 @@ +openapi: 3.0.0 +info: + version: 1.3.0 + title: Cerebrate Project API + description: | + + TODO: markdown description + +servers: + - url: https://cerebrate.local + +tags: + - name: Users + description: "TODO: users resource descriptions" + +paths: + /api/v1/users/view: + get: + summary: "Get information about the current user" + operationId: viewUserMe + tags: + - Users + responses: + "200": + $ref: "#/components/responses/ViewUserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/users/view/{userId}: + get: + summary: "Get information of a user by id" + operationId: viewUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + responses: + "200": + $ref: "#/components/responses/ViewUserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + +components: + schemas: + # General + UUID: + type: string + format: uuid + maxLength: 36 + example: "c99506a6-1255-4b71-afa5-7b8ba48c3b1b" + + ID: + type: integer + format: int32 + example: 1 + + DateTime: + type: string + format: datetime + example: "2022-01-05T11:19:26+00:00" + + # Users + Username: + type: string + example: "admin" + + User: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + username: + $ref: "#/components/schemas/Username" + role_id: + $ref: "#/components/schemas/ID" + individual_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + organisation_id: + $ref: "#/components/schemas/ID" + + # Individuals + + # Organisations + + # Roles + RoleName: + type: string + maxLength: 255 + example: "admin" + + Role: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + name: + $ref: "#/components/schemas/RoleName" + is_default: + type: boolean + perm_admin: + type: boolean + perm_sync: + type: boolean + perm_org_admin: + type: boolean + + # Errors + ApiError: + type: object + required: + - name + - message + - url + properties: + name: + type: string + message: + type: string + url: + type: string + example: "/users" + + UnauthorizedApiError: + type: object + required: + - name + - message + - url + properties: + name: + type: string + example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." + message: + type: string + example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." + url: + type: string + example: "/users" + + NotFoundApiError: + type: object + required: + - name + - message + - url + properties: + name: + type: string + example: "Invalid user" + message: + type: string + example: "Invalid user" + url: + type: string + example: "/users/1234" + + parameters: + userId: + name: userId + in: path + description: "Numeric ID of the User" + required: true + schema: + $ref: "#/components/schemas/ID" + + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: | + The authorization is performed by using the following header in the HTTP requests: + + Authorization: YOUR_API_KEY + + # requestBodies: + + responses: + # User + ViewUserResponse: + description: "User response" + content: + application/json: + schema: + $ref: "#/components/schemas/User" + + # Errors + ApiErrorResponse: + description: "Unexpected API error" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiError" + + UnauthorizedApiErrorResponse: + description: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedApiError" + +security: + - ApiKeyAuth: [] From 5b3bef13e2678dd5a5397bddea2e46f3dd639eeb Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 7 Jan 2022 13:47:20 +0100 Subject: [PATCH 04/36] fix: test --- tests/TestCase/Controller/Users/UsersControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TestCase/Controller/Users/UsersControllerTest.php b/tests/TestCase/Controller/Users/UsersControllerTest.php index 37dd73a..7ef6e4c 100644 --- a/tests/TestCase/Controller/Users/UsersControllerTest.php +++ b/tests/TestCase/Controller/Users/UsersControllerTest.php @@ -23,8 +23,8 @@ class UsersControllerTest extends TestCase $this->enableSecurityToken(); $this->post('/users/login', [ - 'username' => UsersFixture::ADMIN_USER, - 'password' => UsersFixture::ADMIN_PASSWORD, + 'username' => UsersFixture::USER_ADMIN_USERNAME, + 'password' => UsersFixture::USER_ADMIN_PASSWORD, ]); $this->assertSessionHasKey('authUser.uuid'); From 3923064d07c7972c92c1f7f530c41ef53b9d6ff1 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 7 Jan 2022 14:37:04 +0100 Subject: [PATCH 05/36] chg: migrate mysql.sql initial schema to a phinx migration --- INSTALL/INSTALL.md | 6 - INSTALL/mysql.sql | 408 ----- .../20210311000000_InitialSchema.php | 1464 +++++++++++++++++ debian/install | 1 - docker/README.md | 17 - docker/docker-compose.yml | 1 - tests/bootstrap.php | 14 +- 7 files changed, 1469 insertions(+), 442 deletions(-) delete mode 100644 INSTALL/mysql.sql create mode 100644 config/Migrations/20210311000000_InitialSchema.php diff --git a/INSTALL/INSTALL.md b/INSTALL/INSTALL.md index fc6e131..f4cef5f 100644 --- a/INSTALL/INSTALL.md +++ b/INSTALL/INSTALL.md @@ -74,12 +74,6 @@ sudo mysql -e "GRANT ALL PRIVILEGES ON cerebrate.* to cerebrate@localhost;" sudo mysql -e "FLUSH PRIVILEGES;" ``` -Load the default table structure into the database - -```bash -sudo mysql -u cerebrate -p cerebrate < /var/www/cerebrate/INSTALL/mysql.sql -``` - create your local configuration and set the db credentials ```bash diff --git a/INSTALL/mysql.sql b/INSTALL/mysql.sql deleted file mode 100644 index ed39991..0000000 --- a/INSTALL/mysql.sql +++ /dev/null @@ -1,408 +0,0 @@ --- MySQL dump 10.16 Distrib 10.1.44-MariaDB, for debian-linux-gnu (x86_64) --- --- Host: localhost Database: cerebrate --- ------------------------------------------------------ --- Server version 10.1.44-MariaDB-0ubuntu0.18.04.1 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `alignment_tags` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `alignment_tags` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `alignment_id` int(10) unsigned NOT NULL, - `tag_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `alignment_id` (`alignment_id`), - KEY `tag_id` (`tag_id`), - CONSTRAINT `alignment_tags_ibfk_1` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_10` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_11` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_12` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_3` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_4` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_5` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_6` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_7` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_8` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_9` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `alignments` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `alignments` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `individual_id` int(10) unsigned NOT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT 'member', - PRIMARY KEY (`id`), - KEY `individual_id` (`individual_id`), - KEY `organisation_id` (`organisation_id`), - CONSTRAINT `alignments_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `alignments_ibfk_2` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `auth_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `auth_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, - `authkey` varchar(72) CHARACTER SET ascii DEFAULT NULL, - `authkey_start` varchar(4) CHARACTER SET ascii DEFAULT NULL, - `authkey_end` varchar(4) CHARACTER SET ascii DEFAULT NULL, - `created` int(10) unsigned NOT NULL, - `expiration` int(10) unsigned NOT NULL, - `user_id` int(10) unsigned NOT NULL, - `comment` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `authkey_start` (`authkey_start`), - KEY `authkey_end` (`authkey_end`), - KEY `created` (`created`), - KEY `expiration` (`expiration`), - KEY `user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `broods` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `broods` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `url` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - `organisation_id` int(10) unsigned NOT NULL, - `trusted` tinyint(1) DEFAULT NULL, - `pull` tinyint(1) DEFAULT NULL, - `skip_proxy` tinyint(1) DEFAULT NULL, - `authkey` varchar(40) CHARACTER SET ascii DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `name` (`name`), - KEY `url` (`url`), - KEY `authkey` (`authkey`), - KEY `organisation_id` (`organisation_id`), - FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `encryption_key` text COLLATE utf8mb4_unicode_ci, - `revoked` tinyint(1) DEFAULT NULL, - `expires` int(10) unsigned DEFAULT NULL, - `owner_id` int(10) unsigned DEFAULT NULL, - `owner_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `type` (`type`), - KEY `expires` (`expires`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `individual_encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `individual_encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `individual_id` int(10) unsigned NOT NULL, - `encryption_key_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `individual_id` (`individual_id`), - KEY `encryption_key_id` (`encryption_key_id`), - CONSTRAINT `individual_encryption_keys_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_3` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_4` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_5` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_6` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `individuals` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `individuals` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `first_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `last_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `position` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `email` (`email`), - KEY `first_name` (`first_name`), - KEY `last_name` (`last_name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `local_tools` --- - -CREATE TABLE IF NOT EXISTS `local_tools` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `connector` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `settings` text COLLATE utf8mb4_unicode_ci, - `exposed` tinyint(1) NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `connector` (`connector`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- --- Table structure for table `organisation_encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `organisation_encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `organisation_id` int(10) unsigned NOT NULL, - `encryption_key_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `organisation_id` (`organisation_id`), - KEY `encryption_key_id` (`encryption_key_id`), - CONSTRAINT `organisation_encryption_keys_ibfk_1` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`), - CONSTRAINT `organisation_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `organisations` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `organisations` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `url` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `nationality` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `sector` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `contacts` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `name` (`name`), - KEY `url` (`url`), - KEY `nationality` (`nationality`), - KEY `sector` (`sector`), - KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `roles` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `roles` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `is_default` tinyint(1) DEFAULT NULL, - `perm_admin` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `tags` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `tags` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - `colour` varchar(6) CHARACTER SET ascii NOT NULL, - PRIMARY KEY (`id`), - KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `users` --- - -CREATE TABLE IF NOT EXISTS `users` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `username` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `password` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `role_id` int(11) unsigned NOT NULL, - `individual_id` int(11) unsigned NOT NULL, - `disabled` tinyint(1) DEFAULT '0', - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `role_id` (`role_id`), - KEY `individual_id` (`individual_id`), - CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`), - CONSTRAINT `users_ibfk_2` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -CREATE TABLE IF NOT EXISTS `sharing_groups` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `releasability` text DEFAULT NULL, - `description` text DEFAULT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `user_id` int(10) unsigned NOT NULL, - `active` tinyint(1) DEFAULT '1', - `local` tinyint(1) DEFAULT '1', - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `user_id` (`user_id`), - KEY `organisation_id` (`organisation_id`), - KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `sgo` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `sharing_group_id` int(10) unsigned NOT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `deleted` tinyint(1) DEFAULT 0, - PRIMARY KEY (`id`), - KEY `sharing_group_id` (`sharing_group_id`), - KEY `organisation_id` (`organisation_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_fields` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `scope` varchar(191) NOT NULL, - `parent_id` int(10) unsigned NOT NULL, - `field` varchar(191) NOT NULL, - `value` varchar(191) NOT NULL, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `meta_template_id` int(10) unsigned NOT NULL, - `meta_template_field_id` int(10) unsigned NOT NULL, - `is_default` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `scope` (`scope`), - KEY `uuid` (`uuid`), - KEY `parent_id` (`parent_id`), - KEY `field` (`field`), - KEY `value` (`value`), - KEY `meta_template_id` (`meta_template_id`), - KEY `meta_template_field_id` (`meta_template_field_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_templates` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `scope` varchar(191) NOT NULL, - `name` varchar(191) NOT NULL, - `namespace` varchar(191) NOT NULL, - `description` text, - `version` varchar(191) NOT NULL, - `uuid` varchar(40) CHARACTER SET ascii, - `source` varchar(191), - `enabled` tinyint(1) DEFAULT 0, - `is_default` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `scope` (`scope`), - KEY `source` (`source`), - KEY `name` (`name`), - KEY `namespace` (`namespace`), - KEY `version` (`version`), - KEY `uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_template_fields` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `field` varchar(191) NOT NULL, - `type` varchar(191) NOT NULL, - `meta_template_id` int(10) unsigned NOT NULL, - `regex` text, - `multiple` tinyint(1) DEFAULT 0, - `enabled` tinyint(1) DEFAULT 0, - PRIMARY KEY (`id`), - CONSTRAINT `meta_template_id` FOREIGN KEY (`meta_template_id`) REFERENCES `meta_templates` (`id`), - KEY `field` (`field`), - KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `audit_logs` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `created` datetime NOT NULL, - `user_id` int(10) unsigned DEFAULT NULL, - `authkey_id` int(10) unsigned DEFAULT NULL, - `request_ip` varbinary(16) DEFAULT NULL, - `request_type` tinyint NOT NULL, - `request_id` varchar(191) DEFAULT NULL, - `request_action` varchar(20) NOT NULL, - `model` varchar(80) NOT NULL, - `model_id` int(10) unsigned DEFAULT NULL, - `model_title` text DEFAULT NULL, - `change` blob, - PRIMARY KEY (`id`), - KEY `user_id` (`user_id`), - KEY `request_ip` (`request_ip`), - KEY `model` (`model`), - KEY `request_action` (`request_action`), - KEY `model_id` (`model_id`), - KEY `created` (`created`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2020-06-22 14:30:02 diff --git a/config/Migrations/20210311000000_InitialSchema.php b/config/Migrations/20210311000000_InitialSchema.php new file mode 100644 index 0000000..d1e2ed1 --- /dev/null +++ b/config/Migrations/20210311000000_InitialSchema.php @@ -0,0 +1,1464 @@ +execute('SET unique_checks=0; SET foreign_key_checks=0;'); + $this->execute("ALTER DATABASE CHARACTER SET 'utf8mb4';"); + $this->execute("ALTER DATABASE COLLATE='utf8mb4_general_ci';"); + $this->table('broods', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('url', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'url', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'description', + ]) + ->addColumn('trusted', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'organisation_id', + ]) + ->addColumn('pull', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'trusted', + ]) + ->addColumn('skip_proxy', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'pull', + ]) + ->addColumn('authkey', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'skip_proxy', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['url'], [ + 'name' => 'url', + 'unique' => false, + ]) + ->addIndex(['authkey'], [ + 'name' => 'authkey', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'broods_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('sharing_groups', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('releasability', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'releasability', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'description', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'organisation_id', + ]) + ->addColumn('active', 'boolean', [ + 'null' => true, + 'default' => '1', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'user_id', + ]) + ->addColumn('local', 'boolean', [ + 'null' => true, + 'default' => '1', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'active', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->create(); + $this->table('alignment_tags', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('alignment_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('tag_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'alignment_id', + ]) + ->addIndex(['alignment_id'], [ + 'name' => 'alignment_id', + 'unique' => false, + ]) + ->addIndex(['tag_id'], [ + 'name' => 'tag_id', + 'unique' => false, + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_10', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_11', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_12', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_3', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_4', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_5', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_6', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_7', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_8', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_9', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('meta_templates', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('scope', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'scope', + ]) + ->addColumn('namespace', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'namespace', + ]) + ->addColumn('version', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'description', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'version', + ]) + ->addColumn('source', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('enabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'source', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'enabled', + ]) + ->addIndex(['scope'], [ + 'name' => 'scope', + 'unique' => false, + ]) + ->addIndex(['source'], [ + 'name' => 'source', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['namespace'], [ + 'name' => 'namespace', + 'unique' => false, + ]) + ->addIndex(['version'], [ + 'name' => 'version', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->create(); + $this->table('individuals', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('email', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('first_name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'email', + ]) + ->addColumn('last_name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'first_name', + ]) + ->addColumn('position', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'last_name', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['email'], [ + 'name' => 'email', + 'unique' => false, + ]) + ->addIndex(['first_name'], [ + 'name' => 'first_name', + 'unique' => false, + ]) + ->addIndex(['last_name'], [ + 'name' => 'last_name', + 'unique' => false, + ]) + ->create(); + $this->table('organisations', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('url', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('nationality', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'url', + ]) + ->addColumn('sector', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'nationality', + ]) + ->addColumn('type', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'sector', + ]) + ->addColumn('contacts', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'type', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['url'], [ + 'name' => 'url', + 'unique' => false, + ]) + ->addIndex(['nationality'], [ + 'name' => 'nationality', + 'unique' => false, + ]) + ->addIndex(['sector'], [ + 'name' => 'sector', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->create(); + $this->table('encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('type', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('encryption_key', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'type', + ]) + ->addColumn('revoked', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'encryption_key', + ]) + ->addColumn('expires', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'revoked', + ]) + ->addColumn('owner_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'expires', + ]) + ->addColumn('owner_type', 'string', [ + 'null' => false, + 'limit' => 20, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'owner_id', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->addIndex(['expires'], [ + 'name' => 'expires', + 'unique' => false, + ]) + ->create(); + $this->table('meta_fields', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('scope', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('parent_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'scope', + ]) + ->addColumn('field', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'parent_id', + ]) + ->addColumn('value', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'field', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'value', + ]) + ->addColumn('meta_template_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'uuid', + ]) + ->addColumn('meta_template_field_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'meta_template_id', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'meta_template_field_id', + ]) + ->addIndex(['scope'], [ + 'name' => 'scope', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['parent_id'], [ + 'name' => 'parent_id', + 'unique' => false, + ]) + ->addIndex(['field'], [ + 'name' => 'field', + 'unique' => false, + ]) + ->addIndex(['value'], [ + 'name' => 'value', + 'unique' => false, + ]) + ->addIndex(['meta_template_id'], [ + 'name' => 'meta_template_id', + 'unique' => false, + ]) + ->addIndex(['meta_template_field_id'], [ + 'name' => 'meta_template_field_id', + 'unique' => false, + ]) + ->create(); + $this->table('audit_logs', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('created', 'datetime', [ + 'null' => false, + 'after' => 'id', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'created', + ]) + ->addColumn('authkey_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'user_id', + ]) + ->addColumn('request_ip', 'varbinary', [ + 'null' => true, + 'default' => null, + 'limit' => 16, + 'after' => 'authkey_id', + ]) + ->addColumn('request_type', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'request_ip', + ]) + ->addColumn('request_id', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_type', + ]) + ->addColumn('request_action', 'string', [ + 'null' => false, + 'limit' => 20, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_id', + ]) + ->addColumn('model', 'string', [ + 'null' => false, + 'limit' => 80, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_action', + ]) + ->addColumn('model_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'model', + ]) + ->addColumn('model_title', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'model_id', + ]) + ->addColumn('change', 'blob', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::BLOB_REGULAR, + 'after' => 'model_title', + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->addIndex(['request_ip'], [ + 'name' => 'request_ip', + 'unique' => false, + ]) + ->addIndex(['model'], [ + 'name' => 'model', + 'unique' => false, + ]) + ->addIndex(['request_action'], [ + 'name' => 'request_action', + 'unique' => false, + ]) + ->addIndex(['model_id'], [ + 'name' => 'model_id', + 'unique' => false, + ]) + ->addIndex(['created'], [ + 'name' => 'created', + 'unique' => false, + ]) + ->create(); + $this->table('organisation_encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('encryption_key_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'organisation_id', + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addIndex(['encryption_key_id'], [ + 'name' => 'encryption_key_id', + 'unique' => false, + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'organisation_encryption_keys_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'organisation_encryption_keys_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('users', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('username', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('password', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'username', + ]) + ->addColumn('role_id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'signed' => false, + 'after' => 'password', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'signed' => false, + 'after' => 'role_id', + ]) + ->addColumn('disabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'individual_id', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['role_id'], [ + 'name' => 'role_id', + 'unique' => false, + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addForeignKey('role_id', 'roles', 'id', [ + 'constraint' => 'users_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'users_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('roles', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'name', + ]) + ->addColumn('perm_admin', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'is_default', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->create(); + $this->table('tags', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('colour', 'string', [ + 'null' => false, + 'limit' => 6, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'description', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->create(); + $this->table('individual_encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('encryption_key_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'individual_id', + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addIndex(['encryption_key_id'], [ + 'name' => 'encryption_key_id', + 'unique' => false, + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_3', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_4', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_5', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_6', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('meta_template_fields', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('field', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('type', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'field', + ]) + ->addColumn('meta_template_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'type', + ]) + ->addColumn('regex', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'meta_template_id', + ]) + ->addColumn('multiple', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'regex', + ]) + ->addColumn('enabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'multiple', + ]) + ->addIndex(['meta_template_id'], [ + 'name' => 'meta_template_id', + 'unique' => false, + ]) + ->addIndex(['field'], [ + 'name' => 'field', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->addForeignKey('meta_template_id', 'meta_templates', 'id', [ + 'constraint' => 'meta_template_id', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('auth_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => false, + 'limit' => 40, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('authkey', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 72, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'uuid', + ]) + ->addColumn('authkey_start', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 4, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'authkey', + ]) + ->addColumn('authkey_end', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 4, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'authkey_start', + ]) + ->addColumn('created', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'authkey_end', + ]) + ->addColumn('expiration', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'created', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'expiration', + ]) + ->addColumn('comment', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'user_id', + ]) + ->addIndex(['authkey_start'], [ + 'name' => 'authkey_start', + 'unique' => false, + ]) + ->addIndex(['authkey_end'], [ + 'name' => 'authkey_end', + 'unique' => false, + ]) + ->addIndex(['created'], [ + 'name' => 'created', + 'unique' => false, + ]) + ->addIndex(['expiration'], [ + 'name' => 'expiration', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->create(); + $this->table('local_tools', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('connector', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('settings', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'connector', + ]) + ->addColumn('exposed', 'boolean', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'settings', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'exposed', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['connector'], [ + 'name' => 'connector', + 'unique' => false, + ]) + ->create(); + $this->table('alignments', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'individual_id', + ]) + ->addColumn('type', 'string', [ + 'null' => true, + 'default' => 'member', + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'organisation_id', + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'alignments_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'alignments_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('sgo', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('sharing_group_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'sharing_group_id', + ]) + ->addColumn('deleted', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'organisation_id', + ]) + ->addIndex(['sharing_group_id'], [ + 'name' => 'sharing_group_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->create(); + $this->execute('SET unique_checks=1; SET foreign_key_checks=1;'); + } +} diff --git a/debian/install b/debian/install index 2ee55e9..12d7cc8 100755 --- a/debian/install +++ b/debian/install @@ -7,4 +7,3 @@ webroot /usr/share/php-cerebrate config /usr/share/php-cerebrate debian/cerebrate.local.conf /etc/apache2/sites-available/ debian/config.php /etc/cerebrate/ -INSTALL/mysql.sql => /usr/share/dbconfig-common/data/php-cerebrate/install/mysql diff --git a/docker/README.md b/docker/README.md index 1074c0c..9bf0154 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,20 +1,3 @@ -# Database init - -For the `docker-compose` setup to work you must initialize database with -what is in `../INSTALL/mysql.sql` - -``` -mkdir -p run/dbinit/ -cp ../INSTALL/mysql.sql run/dbinit/ -``` - -The MariaDB container has a volume mounted as follow -`- ./run/dbinit:/docker-entrypoint-initdb.d/:ro` - -So that on startup the container will source files in this directory to seed -the database. Once it's done the container will run normally and Cerebrate will -be able to roll its database migration scripts - # Actual data and volumes The actual database will be located in `./run/database` exposed with the diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6aa0b73..821ab5f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -5,7 +5,6 @@ services: restart: always volumes: - ./run/database:/var/lib/mysql - - ./run/dbinit:/docker-entrypoint-initdb.d/:ro environment: MARIADB_RANDOM_ROOT_PASSWORD: "yes" MYSQL_DATABASE: "cerebrate" diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9283095..6df9c27 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -54,18 +54,14 @@ ConnectionManager::alias('test_debug_kit', 'debug_kit'); // has been written to. session_id('cli'); -// Load db schema from mysql.sql and run migrations -// super hacky way to skip migrations +// hacky way to skip migrations if (!in_array('skip-migrations', $_SERVER['argv'])) { - // TODO: Removing mysql.sql and relying only in migrations would be ideal - // in the meantime, `'skip' => ['*']`, prevents migrations from droping already created tables - (new SchemaLoader())->loadSqlFiles('./INSTALL/mysql.sql', 'test'); $migrator = new Migrator(); $migrator->runMany([ - ['connection' => 'test', 'skip' => ['*']], - ['plugin' => 'Tags', 'connection' => 'test', 'skip' => ['*']], - ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test', 'skip' => ['*']] + ['connection' => 'test'], + ['plugin' => 'Tags', 'connection' => 'test'], + ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test'] ]); -}else{ +} else { echo "[ * ] Skipping migrations ...\n"; } From c14b84fcc08e4f99c98b001f08194b4239245fac Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 7 Jan 2022 17:07:09 +0100 Subject: [PATCH 06/36] chg: rename test files --- .../TestCase/Api/Users/IndexUsersApiTest.php | 43 +++++++++++++++ tests/TestCase/Api/Users/ViewUserApiTest.php | 55 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 tests/TestCase/Api/Users/IndexUsersApiTest.php create mode 100644 tests/TestCase/Api/Users/ViewUserApiTest.php diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php new file mode 100644 index 0000000..46e615d --- /dev/null +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -0,0 +1,43 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndex(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + // TODO: $this->validateRequest() + $this->validateResponse(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php new file mode 100644 index 0000000..7c5a346 --- /dev/null +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -0,0 +1,55 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewMe(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + // TODO: $this->validateRequest() + $this->validateResponse(self::ENDPOINT); + } + + public function testViewById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ADMIN_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + // TODO: $this->validateRequest() + $this->validateResponse($url); + } +} From 6776789fdffd6f7cd24ea6a7e7cbe4ecf1ca6298 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 7 Jan 2022 17:08:00 +0100 Subject: [PATCH 07/36] new: add /api/v1/users/index api test --- tests/Helper/ApiTestTrait.php | 7 ++ tests/README.md | 13 +++- tests/TestCase/Api/Users/UsersApiTest.php | 55 -------------- webroot/docs/openapi.yaml | 89 ++++++++++++++++++----- 4 files changed, 90 insertions(+), 74 deletions(-) delete mode 100644 tests/TestCase/Api/Users/UsersApiTest.php diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index e749677..572b7c6 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Test\Helper; +use Cake\Http\Exception\NotImplementedException; use \League\OpenAPIValidation\PSR7\ValidatorBuilder; use \League\OpenAPIValidation\PSR7\RequestValidator; use \League\OpenAPIValidation\PSR7\ResponseValidator; @@ -38,6 +39,12 @@ trait ApiTestTrait ]); } + public function assertResponseContainsArray(array $expected): void + { + $responseArray = json_decode((string)$this->_response->getBody(), true); + throw new NotImplementedException('TODO: see codeception seeResponseContainsJson()'); + } + /** * Parse the OpenAPI specification and create a validator * diff --git a/tests/README.md b/tests/README.md index f37bcf3..732aafc 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,5 +1,5 @@ # Testing - +## Configuration 1. Add a `cerebrate_test` database to the db: ```mysql CREATE DATABASE cerebrate_test; @@ -55,3 +55,14 @@ By default the database is re-generated before running the test suite, to skip t ``` $ vendor/bin/phpunit -d skip-migrations ``` + +## Coverage +HTML: +``` +$ vendor/bin/phpunit --coverage-html tmp/coverage +``` + +XML: +``` +$ vendor/bin/phpunit --verbose --coverage-clover=coverage.xml +``` diff --git a/tests/TestCase/Api/Users/UsersApiTest.php b/tests/TestCase/Api/Users/UsersApiTest.php deleted file mode 100644 index 2f2c2b8..0000000 --- a/tests/TestCase/Api/Users/UsersApiTest.php +++ /dev/null @@ -1,55 +0,0 @@ -initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - - public function testViewMe(): void - { - $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); - $this->get(self::ENDPOINT); - - $this->assertResponseOk(); - $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->validateRequest() - $this->validateResponse(self::ENDPOINT); - } - - public function testViewById(): void - { - $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); - $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ADMIN_ID); - $this->get($url); - - $this->assertResponseOk(); - $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->validateRequest() - $this->validateResponse($url); - } -} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 5a55225..00fd682 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -11,9 +11,25 @@ servers: tags: - name: Users - description: "TODO: users resource descriptions" + description: "Users enrolled in this Cerebrate instance." paths: + /api/v1/users/index: + get: + summary: "Get users list" + operationId: getUsers + tags: + - Users + responses: + "200": + $ref: "#/components/responses/GetUsersResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/users/view: get: summary: "Get information about the current user" @@ -22,7 +38,7 @@ paths: - Users responses: "200": - $ref: "#/components/responses/ViewUserResponse" + $ref: "#/components/responses/GetUserResponse" "403": $ref: "#/components/responses/UnauthorizedApiErrorResponse" default: @@ -38,7 +54,7 @@ paths: - $ref: "#/components/parameters/userId" responses: "200": - $ref: "#/components/responses/ViewUserResponse" + $ref: "#/components/responses/GetUserResponse" "403": $ref: "#/components/responses/UnauthorizedApiErrorResponse" default: @@ -90,6 +106,11 @@ components: organisation_id: $ref: "#/components/schemas/ID" + UserList: + type: array + items: + $ref: "#/components/schemas/User" + # Individuals # Organisations @@ -120,51 +141,69 @@ components: ApiError: type: object required: - - name - message - url + - code properties: - name: - type: string message: type: string url: type: string - example: "/users" + example: "/api/v1/users" + code: + type: integer + example: 500 UnauthorizedApiError: type: object required: - - name - message - url + - code properties: - name: - type: string - example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." message: type: string example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." url: type: string - example: "/users" + example: "/api/v1/users" + code: + type: integer + example: 403 + + MethodNotAllowedApiError: + type: object + required: + - message + - url + - code + properties: + message: + type: string + example: "You do not have permission to use this functionality." + url: + type: string + example: "/api/v1/users/index" + code: + type: integer + example: 405 NotFoundApiError: type: object required: - - name - message - url + - code properties: - name: - type: string - example: "Invalid user" message: type: string example: "Invalid user" url: type: string - example: "/users/1234" + example: "/api/v1/users/users/view/1234" + code: + type: integer + example: 404 parameters: userId: @@ -189,13 +228,20 @@ components: responses: # User - ViewUserResponse: + GetUserResponse: description: "User response" content: application/json: schema: $ref: "#/components/schemas/User" + GetUsersResponse: + description: "User response" + content: + application/json: + schema: + $ref: "#/components/schemas/UserList" + # Errors ApiErrorResponse: description: "Unexpected API error" @@ -211,5 +257,12 @@ components: schema: $ref: "#/components/schemas/UnauthorizedApiError" + MethodNotAllowedApiErrorResponse: + description: "Method not allowed. Your User Role is not allowed to access this resource." + content: + application/json: + schema: + $ref: "#/components/schemas/MethodNotAllowedApiError" + security: - ApiKeyAuth: [] From 28650fa91ca7a5725abfd8b23edda802d2ba61c6 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 10 Jan 2022 11:58:52 +0100 Subject: [PATCH 08/36] add: add users add/edit/delete api tests and openapi docs. --- tests/Fixture/AuthKeysFixture.php | 8 +- tests/Helper/ApiTestTrait.php | 44 ++++++++- tests/TestCase/Api/Users/AddUserApiTest.php | 77 ++++++++++++++++ .../TestCase/Api/Users/DeleteUserApiTest.php | 60 ++++++++++++ tests/TestCase/Api/Users/EditUserApiTest.php | 91 +++++++++++++++++++ .../TestCase/Api/Users/IndexUsersApiTest.php | 6 +- tests/TestCase/Api/Users/ViewUserApiTest.php | 20 ++-- 7 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 tests/TestCase/Api/Users/AddUserApiTest.php create mode 100644 tests/TestCase/Api/Users/DeleteUserApiTest.php create mode 100644 tests/TestCase/Api/Users/EditUserApiTest.php diff --git a/tests/Fixture/AuthKeysFixture.php b/tests/Fixture/AuthKeysFixture.php index 8291811..802ca5c 100644 --- a/tests/Fixture/AuthKeysFixture.php +++ b/tests/Fixture/AuthKeysFixture.php @@ -14,7 +14,7 @@ class AuthKeysFixture extends TestFixture public const ADMIN_API_KEY = 'd033e22ae348aeb5660fc2140aec35850c4da997'; public const SYNC_API_KEY = '6b387ced110858dcbcda36edb044dc18f91a0894'; public const ORG_ADMIN_API_KEY = '1c4685d281d478dbcebd494158024bc3539004d0'; - public const USER_API_KEY = '12dea96fec20593566ab75692c9949596833adc9'; + public const REGULAR_USER_API_KEY = '12dea96fec20593566ab75692c9949596833adc9'; public function init(): void { @@ -57,9 +57,9 @@ class AuthKeysFixture extends TestFixture ], [ 'uuid' => $faker->uuid(), - 'authkey' => $hasher->hash(self::USER_API_KEY), - 'authkey_start' => substr(self::USER_API_KEY, 0, 4), - 'authkey_end' => substr(self::USER_API_KEY, -4), + 'authkey' => $hasher->hash(self::REGULAR_USER_API_KEY), + 'authkey_start' => substr(self::REGULAR_USER_API_KEY, 0, 4), + 'authkey_end' => substr(self::REGULAR_USER_API_KEY, -4), 'expiration' => 0, 'user_id' => UsersFixture::USER_REGULAR_USER_ID, 'comment' => '', diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 572b7c6..21b0c8b 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -65,7 +65,7 @@ trait ApiTestTrait * @param string $method The HTTP method used to call the endpoint * @return void */ - public function validateRequest(string $endpoint, string $method = 'get'): void + public function assertRequestMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void { // TODO: find a workaround to create a PSR-7 request object for validation throw NotImplementedException("Unfortunately cakephp does not save the PSR-7 request object in the test context"); @@ -78,9 +78,49 @@ trait ApiTestTrait * @param string $method The HTTP method used to call the endpoint * @return void */ - public function validateResponse(string $endpoint, string $method = 'get'): void + public function assertResponseMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void { $address = new OperationAddress($endpoint, $method); $this->responseValidator->validate($address, $this->_response); } + + /** + * Validates a record exists in the database + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return void + * @throws \Exception + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * + * @see https://book.cakephp.org/4/en/orm-query-builder.html + */ + public function assertDbRecordExists(string $table, array $conditions): void + { + $record = $this->getTableLocator()->get($table)->find()->where($conditions)->first(); + if (!$record) { + throw new \PHPUnit\Framework\AssertionFailedError("Record not found in table '$table' with conditions: " . json_encode($conditions)); + } + $this->assertNotEmpty($record); + } + + /** + * Validates a record do notexists in the database + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return void + * @throws \Exception + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * + * @see https://book.cakephp.org/4/en/orm-query-builder.html + */ + public function assertDbRecordNotExists(string $table, array $conditions): void + { + $record = $this->getTableLocator()->get($table)->find()->where($conditions)->first(); + if ($record) { + throw new \PHPUnit\Framework\AssertionFailedError("Record found in table '$table' with conditions: " . json_encode($conditions)); + } + $this->assertEmpty($record); + } } diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php new file mode 100644 index 0000000..b3d85a3 --- /dev/null +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -0,0 +1,77 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddUser(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'disabled' => false, + 'username' => 'test', + 'password' => 'Password123456!', + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"username": "test"'); + $this->assertDbRecordExists('Users', ['username' => 'test']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddUserNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'disabled' => false, + 'username' => 'test', + 'password' => 'Password123456!' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Users', ['username' => 'test']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php new file mode 100644 index 0000000..dad2d1f --- /dev/null +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -0,0 +1,60 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteUser(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Users', ['id' => UsersFixture::USER_REGULAR_USER_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteUserNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ORG_ADMIN_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Users', ['id' => UsersFixture::USER_ORG_ADMIN_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php new file mode 100644 index 0000000..6c94877 --- /dev/null +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -0,0 +1,91 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditUser(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); + $this->put( + $url, + [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID, + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists('Users', [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditRoleNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->put( + self::ENDPOINT, + [ + 'role_id' => RolesFixture::ROLE_ADMIN_ID, + ] + ); + + $this->assertDbRecordNotExists('Users', [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ADMIN_ID + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); + } + + public function testEditSelfUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->put( + self::ENDPOINT, + [ + 'username' => 'test', + ] + ); + + $this->assertDbRecordExists('Users', [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'username' => 'test' + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); + } +} diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php index 46e615d..6721e77 100644 --- a/tests/TestCase/Api/Users/IndexUsersApiTest.php +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -30,14 +30,14 @@ class IndexUsersApiTest extends TestCase $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); } - public function testIndex(): void + public function testIndexUsers(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); $this->get(self::ENDPOINT); $this->assertResponseOk(); $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->validateRequest() - $this->validateResponse(self::ENDPOINT); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php index 7c5a346..34c9c62 100644 --- a/tests/TestCase/Api/Users/ViewUserApiTest.php +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -30,26 +30,26 @@ class ViewUserApiTest extends TestCase $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); } - public function testViewMe(): void + public function testViewMyUser(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); $this->get(self::ENDPOINT); $this->assertResponseOk(); $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->validateRequest() - $this->validateResponse(self::ENDPOINT); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } - - public function testViewById(): void + + public function testViewUserById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); - $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ADMIN_ID); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); $this->get($url); - + $this->assertResponseOk(); - $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->validateRequest() - $this->validateResponse($url); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_REGULAR_USER_USERNAME)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); } } From ce1a51cc3903315f127842c193ac7b4e7fb47bce Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 10 Jan 2022 11:59:23 +0100 Subject: [PATCH 09/36] fix: incorrect check --- src/Controller/Component/ParamHandlerComponent.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/Component/ParamHandlerComponent.php b/src/Controller/Component/ParamHandlerComponent.php index 89d6cce..e52e5f8 100644 --- a/src/Controller/Component/ParamHandlerComponent.php +++ b/src/Controller/Component/ParamHandlerComponent.php @@ -4,6 +4,7 @@ namespace App\Controller\Component; use Cake\Controller\Component; use Cake\Core\Configure; +use Cake\Http\Exception\MethodNotAllowedException; class ParamHandlerComponent extends Component { @@ -47,7 +48,7 @@ class ParamHandlerComponent extends Component return $this->isRest; } if ($this->request->is('json')) { - if (!empty($this->request->getBody()) && !empty($this->request->getParsedBody())) { + if (!empty((string)$this->request->getBody()) && empty($this->request->getParsedBody())) { throw new MethodNotAllowedException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.'); } $this->isRest = true; From 71072f60eb8c5d9b68c19a4e750d8f3ecb37ce3e Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 10 Jan 2022 11:59:55 +0100 Subject: [PATCH 10/36] chg: extend openapi spec --- webroot/docs/openapi.yaml | 134 +++++++++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 9 deletions(-) diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 00fd682..f0f184f 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -3,8 +3,7 @@ info: version: 1.3.0 title: Cerebrate Project API description: | - - TODO: markdown description + Cerebrate is an open-source platform meant to act as a trusted contact information provider and interconnection orchestrator for other security tools. servers: - url: https://cerebrate.local @@ -22,7 +21,7 @@ paths: - Users responses: "200": - $ref: "#/components/responses/GetUsersResponse" + $ref: "#/components/responses/UserListResponse" "403": $ref: "#/components/responses/UnauthorizedApiErrorResponse" "405": @@ -38,9 +37,11 @@ paths: - Users responses: "200": - $ref: "#/components/responses/GetUserResponse" + $ref: "#/components/responses/UserResponse" "403": $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" default: $ref: "#/components/responses/ApiErrorResponse" @@ -54,9 +55,85 @@ paths: - $ref: "#/components/parameters/userId" responses: "200": - $ref: "#/components/responses/GetUserResponse" + $ref: "#/components/responses/UserResponse" "403": $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/users/add: + post: + summary: "Add user" + operationId: addUser + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/AddUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/users/edit: + put: + summary: "Edit current user" + operationId: editUser + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/EditUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/users/edit/{userId}: + put: + summary: "Edit current user" + operationId: editUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + requestBody: + $ref: "#/components/requestBodies/EditUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/users/delete/{userId}: + delete: + summary: "Delete user by ID" + operationId: deleteUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" default: $ref: "#/components/responses/ApiErrorResponse" @@ -224,19 +301,58 @@ components: Authorization: YOUR_API_KEY - # requestBodies: + requestBodies: + AddUserRequest: + required: true + content: + application/json: + schema: + type: object + properties: + individual_id: + $ref: "#/components/schemas/ID" + organisation_id: + $ref: "#/components/schemas/ID" + role_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + username: + $ref: "#/components/schemas/Username" + password: + type: string + + EditUserRequest: + required: true + content: + application/json: + schema: + type: object + properties: + individual_id: + $ref: "#/components/schemas/ID" + organisation_id: + $ref: "#/components/schemas/ID" + role_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + username: + $ref: "#/components/schemas/Username" + password: + type: string responses: # User - GetUserResponse: + UserResponse: description: "User response" content: application/json: schema: $ref: "#/components/schemas/User" - GetUsersResponse: - description: "User response" + UserListResponse: + description: "Users list response" content: application/json: schema: From b954e1106434a2dd03433e712816626b6a49e17f Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 10 Jan 2022 16:19:58 +0100 Subject: [PATCH 11/36] add: add/edit operations api tests and openapi spec --- tests/README.md | 4 +- .../Organisations/AddOrganisationApiTest.php | 85 +++++++++ .../Organisations/EditOrganisationApiTest.php | 81 +++++++++ tests/TestCase/Api/Users/AddUserApiTest.php | 1 + .../TestCase/Api/Users/DeleteUserApiTest.php | 1 + tests/TestCase/Api/Users/EditUserApiTest.php | 1 + .../TestCase/Api/Users/IndexUsersApiTest.php | 1 + tests/TestCase/Api/Users/ViewUserApiTest.php | 1 + webroot/docs/openapi.yaml | 166 ++++++++++++++++++ 9 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 tests/TestCase/Api/Organisations/AddOrganisationApiTest.php create mode 100644 tests/TestCase/Api/Organisations/EditOrganisationApiTest.php diff --git a/tests/README.md b/tests/README.md index 732aafc..0486b3b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -31,7 +31,7 @@ QUIT; ``` $ composer install -$ vendor/bin/phpunit +$ vendor/bin/phpunit PHPUnit 8.5.22 by Sebastian Bergmann and contributors. ..... 5 / 5 (100%) @@ -43,7 +43,7 @@ OK (5 tests, 15 assertions) Running a specific suite: ``` -$ vendor/bin/phpunit --testsuite=api +$ vendor/bin/phpunit --testsuite=api --testdox ``` Available suites: * `app`: runs all test suites diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php new file mode 100644 index 0000000..4f43a09 --- /dev/null +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -0,0 +1,85 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddOrganisation(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'name' => 'Test Organisation', + 'description' => $faker->text, + 'uuid' => $uuid, + 'url' => 'http://example.com', + 'nationality' => 'US', + 'sector' => 'sector', + 'type' => 'type', + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('Organisations', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddOrganisationNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'name' => 'Test Organisation', + 'description' => $faker->text, + 'uuid' => $uuid, + 'url' => 'http://example.com', + 'nationality' => 'US', + 'sector' => 'sector', + 'type' => 'type', + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Organisations', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php new file mode 100644 index 0000000..61b2cab --- /dev/null +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -0,0 +1,81 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditOrganisation(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Organisation 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Organisations', + [ + 'id' => OrganisationsFixture::ORGANISATION_A_ID, + 'name' => 'Test Organisation 4321', + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditOrganisationNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Organisation 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'id' => OrganisationsFixture::ORGANISATION_B_ID, + 'name' => 'Test Organisation 1234' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index b3d85a3..2d23f2a 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -20,6 +20,7 @@ class AddUserApiTest extends TestCase protected const ENDPOINT = '/api/v1/users/add'; protected $fixtures = [ + 'app.Organisations', 'app.Individuals', 'app.Roles', 'app.Users', diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index dad2d1f..47a0580 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -20,6 +20,7 @@ class DeleteUserApiTest extends TestCase protected const ENDPOINT = '/api/v1/users/delete'; protected $fixtures = [ + 'app.Organisations', 'app.Individuals', 'app.Roles', 'app.Users', diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 6c94877..5ac568a 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -20,6 +20,7 @@ class EditUserApiTest extends TestCase protected const ENDPOINT = '/api/v1/users/edit'; protected $fixtures = [ + 'app.Organisations', 'app.Individuals', 'app.Roles', 'app.Users', diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php index 6721e77..403a046 100644 --- a/tests/TestCase/Api/Users/IndexUsersApiTest.php +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -18,6 +18,7 @@ class IndexUsersApiTest extends TestCase protected const ENDPOINT = '/api/v1/users/index'; protected $fixtures = [ + 'app.Organisations', 'app.Individuals', 'app.Roles', 'app.Users', diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php index 34c9c62..99bdafe 100644 --- a/tests/TestCase/Api/Users/ViewUserApiTest.php +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -18,6 +18,7 @@ class ViewUserApiTest extends TestCase protected const ENDPOINT = '/api/v1/users/view'; protected $fixtures = [ + 'app.Organisations', 'app.Individuals', 'app.Roles', 'app.Users', diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index f0f184f..a2a161e 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -11,6 +11,8 @@ servers: tags: - name: Users description: "Users enrolled in this Cerebrate instance." + - name: Organisations + description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." paths: /api/v1/users/index: @@ -137,6 +139,44 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/organisations/add: + post: + summary: "Add organisation" + operationId: addOrganisation + tags: + - Organisations + requestBody: + $ref: "#/components/requestBodies/AddOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/organisations/edit/{organisationId}: + put: + summary: "Edit organisation" + operationId: editOrganisation + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/EditOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -191,6 +231,73 @@ components: # Individuals # Organisations + OrganisationName: + type: string + + OrganisationUrl: + type: string + + OrganisationSector: + type: string + nullable: true + + OrganisationType: + type: string + nullable: true + + OrganisationContacts: + type: string + nullable: true + + OrganisationNationality: + type: string + nullable: true + + Organisation: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + tags: + $ref: "#/components/schemas/TagList" + aligments: + $ref: "#/components/schemas/AligmentList" + + # Tags + Tag: + type: object + + TagList: + type: array + items: + $ref: "#/components/schemas/Tag" + + # Alignments + Alignment: + type: object + + AligmentList: + type: array + items: + $ref: "#/components/schemas/Alignment" # Roles RoleName: @@ -291,6 +398,14 @@ components: schema: $ref: "#/components/schemas/ID" + organisationId: + name: organisationId + in: path + description: "Numeric ID of the Organisation" + required: true + schema: + $ref: "#/components/schemas/ID" + securitySchemes: ApiKeyAuth: type: apiKey @@ -342,6 +457,50 @@ components: password: type: string + AddOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + + EditOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + responses: # User UserResponse: @@ -358,6 +517,13 @@ components: schema: $ref: "#/components/schemas/UserList" + OrganisationResponse: + description: "Organisation response" + content: + application/json: + schema: + $ref: "#/components/schemas/Organisation" + # Errors ApiErrorResponse: description: "Unexpected API error" From 241e760ad2823ea91c2bba46eb5a84bdf25b9d0d Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 10 Jan 2022 16:20:22 +0100 Subject: [PATCH 12/36] add: add API menu option --- src/Controller/Component/Navigation/sidemenu.php | 7 ++++++- src/Controller/Component/NavigationComponent.php | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Controller/Component/Navigation/sidemenu.php b/src/Controller/Component/Navigation/sidemenu.php index fcca213..56a0154 100644 --- a/src/Controller/Component/Navigation/sidemenu.php +++ b/src/Controller/Component/Navigation/sidemenu.php @@ -45,7 +45,12 @@ class Sidemenu { 'label' => __('Broods'), 'icon' => $this->iconTable['Broods'], 'url' => '/broods/index', - ] + ], + 'API' => [ + 'label' => __('API'), + 'icon' => $this->iconTable['API'], + 'url' => '/api/index', + ], ], __('Administration') => [ 'Roles' => [ diff --git a/src/Controller/Component/NavigationComponent.php b/src/Controller/Component/NavigationComponent.php index f0dd453..b8caee7 100644 --- a/src/Controller/Component/NavigationComponent.php +++ b/src/Controller/Component/NavigationComponent.php @@ -34,6 +34,7 @@ class NavigationComponent extends Component 'LocalTools' => 'tools', 'Instance' => 'server', 'Tags' => 'tags', + 'API' => 'code', ]; public function initialize(array $config): void From f774f68ede3c84b566cbb64699898125c54c03a5 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 11 Jan 2022 12:33:34 +0100 Subject: [PATCH 13/36] add: add api tests for tags and orgs, extend openapi spec, fix routes for tags plugin --- config/routes.php | 15 +- tests/Fixture/TagsTaggedsFixture.php | 40 ++++ tests/Fixture/TagsTagsFixture.php | 87 ++++++++ .../DeleteOrganisationApiTest.php | 59 ++++++ .../IndexOrganisationsApiTest.php | 45 ++++ .../Organisations/TagOrganisationApiTest.php | 86 ++++++++ .../UntagOrganisationApiTest.php | 86 ++++++++ tests/TestCase/Api/Tags/IndexTagsApiTest.php | 47 +++++ tests/TestCase/Api/Users/EditUserApiTest.php | 1 - webroot/docs/openapi.yaml | 194 +++++++++++++++++- 10 files changed, 657 insertions(+), 3 deletions(-) create mode 100644 tests/Fixture/TagsTaggedsFixture.php create mode 100644 tests/Fixture/TagsTagsFixture.php create mode 100644 tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php create mode 100644 tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php create mode 100644 tests/TestCase/Api/Organisations/TagOrganisationApiTest.php create mode 100644 tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php create mode 100644 tests/TestCase/Api/Tags/IndexTagsApiTest.php diff --git a/config/routes.php b/config/routes.php index e467725..d51121a 100644 --- a/config/routes.php +++ b/config/routes.php @@ -101,5 +101,18 @@ $routes->scope('/api', function (RouteBuilder $routes) { // Generic API route $routes->connect('/{controller}/{action}/*'); + + // Tags plugin routes + $routes->plugin( + 'tags', + ['path' => '/tags'], + function ($routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->connect( + '/{action}/*', + ['controller' => 'Tags'] + ); + } + ); }); -}); \ No newline at end of file +}); diff --git a/tests/Fixture/TagsTaggedsFixture.php b/tests/Fixture/TagsTaggedsFixture.php new file mode 100644 index 0000000..e2bf70f --- /dev/null +++ b/tests/Fixture/TagsTaggedsFixture.php @@ -0,0 +1,40 @@ +records = [ + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_B_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'fk_model' => 'Organisations', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/Fixture/TagsTagsFixture.php b/tests/Fixture/TagsTagsFixture.php new file mode 100644 index 0000000..002bc9e --- /dev/null +++ b/tests/Fixture/TagsTagsFixture.php @@ -0,0 +1,87 @@ +records = [ + [ + 'id' => self::TAG_RED_ID, + 'name' => 'red', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => 'FF0000', + 'counter' => 0, + 'text_colour' => 'red', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_GREEN_ID, + 'name' => 'green', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '00FF00', + 'counter' => 0, + 'text_colour' => 'green', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_BLUE_ID, + 'name' => 'blue', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '0000FF', + 'counter' => 0, + 'text_colour' => 'blue', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_ORG_A_ID, + 'name' => 'org-a', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '000000', + 'counter' => 0, + 'text_colour' => 'black', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_ORG_B_ID, + 'name' => 'org-b', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '000000', + 'counter' => 0, + 'text_colour' => 'black', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php new file mode 100644 index 0000000..9840faf --- /dev/null +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -0,0 +1,59 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteOrganisation(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteOrganisationNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php new file mode 100644 index 0000000..ba7c255 --- /dev/null +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -0,0 +1,45 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexOrganisations(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_A_ID)); + $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_B_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php new file mode 100644 index 0000000..e23fcbc --- /dev/null +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -0,0 +1,86 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testTagOrganisation(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"red\"]" + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_RED_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } + + public function testTagOrganisationNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"green\"]" + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_GREEN_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } +} diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php new file mode 100644 index 0000000..0c23ca4 --- /dev/null +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -0,0 +1,86 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testUntagOrganisation(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"org-a\"]" + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } + + public function testUntagOrganisationNotAllowedToRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"org-a\"]" + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } +} diff --git a/tests/TestCase/Api/Tags/IndexTagsApiTest.php b/tests/TestCase/Api/Tags/IndexTagsApiTest.php new file mode 100644 index 0000000..137151f --- /dev/null +++ b/tests/TestCase/Api/Tags/IndexTagsApiTest.php @@ -0,0 +1,47 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexTags(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "red"'); + $this->assertResponseContains('"name": "green"'); + $this->assertResponseContains('"name": "blue"'); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 5ac568a..6154c00 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -8,7 +8,6 @@ use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; -use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\RolesFixture; use App\Test\Helper\ApiTestTrait; diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index a2a161e..f76a861 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -13,6 +13,8 @@ tags: description: "Users enrolled in this Cerebrate instance." - name: Organisations description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." + - name: Tags + description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." paths: /api/v1/users/index: @@ -177,6 +179,96 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/organisations/index: + get: + summary: "Get organisations" + operationId: getOrganisations + tags: + - Organisations + responses: + "200": + $ref: "#/components/responses/OrganisationListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/organisations/delete/{organisationId}: + delete: + summary: "Delete organisation by ID" + operationId: deleteOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/organisations/tag/{organisationId}: + post: + summary: "Tag organisation by ID" + operationId: tagOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/TagOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/organisations/untag/{organisationId}: + post: + summary: "Remove organisation tag by ID" + operationId: untagOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/UntagOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/tags/index: + get: + summary: "Get tags list" + operationId: getTags + tags: + - Tags + responses: + "200": + $ref: "#/components/responses/TagListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -281,9 +373,60 @@ components: aligments: $ref: "#/components/schemas/AligmentList" + OrganisationList: + type: array + items: + $ref: "#/components/schemas/Organisation" + # Tags + TagName: + type: string + example: "white" + + TagNamespace: + type: string + nullable: true + example: "tlp" + + TagPredicate: + type: string + nullable: true + + TagValue: + type: string + nullable: true + + TagColour: + type: string + example: "FFFFFF" + + TagTextColour: + type: string + example: "white" + Tag: type: object + properties: + id: + $ref: "#/components/schemas/ID" + name: + $ref: "#/components/schemas/TagName" + namespace: + $ref: "#/components/schemas/TagNamespace" + predicate: + $ref: "#/components/schemas/TagPredicate" + value: + $ref: "#/components/schemas/TagValue" + colour: + $ref: "#/components/schemas/TagColour" + text_colour: + $ref: "#/components/schemas/TagTextColour" + counter: + type: integer + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" TagList: type: array @@ -417,6 +560,7 @@ components: Authorization: YOUR_API_KEY requestBodies: + # Users AddUserRequest: required: true content: @@ -457,6 +601,7 @@ components: password: type: string + # Organisations AddOrganisationRequest: required: true content: @@ -501,8 +646,32 @@ components: contacts: $ref: "#/components/schemas/OrganisationContacts" + TagOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + tag_list: + type: string + description: "Stringified JSON array of the tag names to add." + example: '["red"]' + + UntagOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + tag_list: + type: string + description: "Stringified JSON array of the tag names to remove." + example: '["red"]' + responses: - # User + # Users UserResponse: description: "User response" content: @@ -517,6 +686,7 @@ components: schema: $ref: "#/components/schemas/UserList" + # Organisations OrganisationResponse: description: "Organisation response" content: @@ -524,6 +694,28 @@ components: schema: $ref: "#/components/schemas/Organisation" + OrganisationListResponse: + description: "Organisations list response" + content: + application/json: + schema: + $ref: "#/components/schemas/OrganisationList" + + # Tags + TagResponse: + description: "Tag response" + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + + TagListResponse: + description: "Tags list response" + content: + application/json: + schema: + $ref: "#/components/schemas/TagList" + # Errors ApiErrorResponse: description: "Unexpected API error" From 5906f6d2c768cf77d7fac45488a66fbc7dec554f Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 11 Jan 2022 17:17:04 +0100 Subject: [PATCH 14/36] add: add individuals api tests and extend openapi spec --- tests/Fixture/IndividualsFixture.php | 18 +- tests/Fixture/OrganisationsFixture.php | 3 - .../Api/Individuals/AddIndividualApiTest.php | 71 ++++++ .../Individuals/DeleteIndividualApiTest.php | 59 +++++ .../Api/Individuals/EditIndividualApiTest.php | 73 ++++++ .../Individuals/IndexIndividualsApiTest.php | 44 ++++ .../Organisations/AddOrganisationApiTest.php | 2 +- .../DeleteOrganisationApiTest.php | 2 +- .../Organisations/EditOrganisationApiTest.php | 2 +- .../Organisations/TagOrganisationApiTest.php | 2 +- .../UntagOrganisationApiTest.php | 2 +- .../Organisations/ViewOrganisationApiTest.php | 48 ++++ tests/TestCase/Api/Users/AddUserApiTest.php | 2 +- .../TestCase/Api/Users/DeleteUserApiTest.php | 2 +- tests/TestCase/Api/Users/EditUserApiTest.php | 2 +- webroot/docs/openapi.yaml | 225 +++++++++++++++++- 16 files changed, 537 insertions(+), 20 deletions(-) create mode 100644 tests/TestCase/Api/Individuals/AddIndividualApiTest.php create mode 100644 tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php create mode 100644 tests/TestCase/Api/Individuals/EditIndividualApiTest.php create mode 100644 tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php create mode 100644 tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php index 9c96f8b..13aecdf 100644 --- a/tests/Fixture/IndividualsFixture.php +++ b/tests/Fixture/IndividualsFixture.php @@ -10,17 +10,11 @@ class IndividualsFixture extends TestFixture { public $connection = 'test'; - // Admin individual public const INDIVIDUAL_ADMIN_ID = 1; - - // Sync individual public const INDIVIDUAL_SYNC_ID = 2; - - // Org Admin individual public const INDIVIDUAL_ORG_ADMIN_ID = 3; - - // Regular User individual public const INDIVIDUAL_REGULAR_USER_ID = 4; + public const INDIVIDUAL_A_ID = 5; public function init(): void { @@ -66,6 +60,16 @@ class IndividualsFixture extends TestFixture 'position' => 'user', 'created' => $faker->dateTime()->getTimestamp(), 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_A_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'user', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() ] ]; parent::init(); diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php index d8b81f9..7b7439e 100644 --- a/tests/Fixture/OrganisationsFixture.php +++ b/tests/Fixture/OrganisationsFixture.php @@ -11,10 +11,7 @@ class OrganisationsFixture extends TestFixture { public $connection = 'test'; - // Organisation A public const ORGANISATION_A_ID = 1; - - // Organisation B public const ORGANISATION_B_ID = 2; public function init(): void diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php new file mode 100644 index 0000000..64855e8 --- /dev/null +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -0,0 +1,71 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddIndividual(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'email' => 'john@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'position' => 'Security Analyst' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists('Individuals', ['email' => 'john@example.com']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddUserNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'email' => 'john@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'position' => 'Security Analyst' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Individuals', ['email' => 'john@example.com']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php new file mode 100644 index 0000000..c493c4b --- /dev/null +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -0,0 +1,59 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteIndividual(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php new file mode 100644 index 0000000..fcef7fd --- /dev/null +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -0,0 +1,73 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditIndividualAsAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, + 'email' => 'foo@bar.com' + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditAnyIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, + 'email' => 'foo@bar.com' + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php new file mode 100644 index 0000000..55f6be1 --- /dev/null +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -0,0 +1,44 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexIndividuals(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php index 4f43a09..a3f2585 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -57,7 +57,7 @@ class AddOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } - public function testAddOrganisationNotAllowedToRegularUser(): void + public function testAddOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index 9840faf..e16f57b 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -44,7 +44,7 @@ class DeleteOrganisationApiTest extends TestCase $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } - public function testDeleteOrganisationNotAllowedToRegularUser(): void + public function testDeleteOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php index 61b2cab..d9cc7c6 100644 --- a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -55,7 +55,7 @@ class EditOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'put'); } - public function testEditOrganisationNotAllowedToRegularUser(): void + public function testEditOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php index e23fcbc..ed74c74 100644 --- a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -59,7 +59,7 @@ class TagOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'post'); } - public function testTagOrganisationNotAllowedToRegularUser(): void + public function testTagOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php index 0c23ca4..c8878d6 100644 --- a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -59,7 +59,7 @@ class UntagOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'post'); } - public function testUntagOrganisationNotAllowedToRegularUser(): void + public function testUntagOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php new file mode 100644 index 0000000..630fd92 --- /dev/null +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -0,0 +1,48 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewOrganisationById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "Organisation A"'); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index 2d23f2a..e60cf3a 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -55,7 +55,7 @@ class AddUserApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } - public function testAddUserNotAllowedToRegularUser(): void + public function testAddUserNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $this->post( diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 47a0580..69bd87c 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -46,7 +46,7 @@ class DeleteUserApiTest extends TestCase $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } - public function testDeleteUserNotAllowedToRegularUser(): void + public function testDeleteUserNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ORG_ADMIN_ID); diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 6154c00..a8afa11 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -53,7 +53,7 @@ class EditUserApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'put'); } - public function testEditRoleNotAllowedToRegularUser(): void + public function testEditRoleNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $this->put( diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index f76a861..d74ad68 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -9,6 +9,8 @@ servers: - url: https://cerebrate.local tags: + - name: Individuals + description: "Individuals are natural persons. They are meant to describe the basic information about an individual that may or may not be a user of this community. Users in genral require an individual object to identify the person behind them - however, no user account is required to store information about an individual. Individuals can have affiliations to organisations and broods as well as cryptographic keys, using which their messages can be verified and which can be used to securely contact them." - name: Users description: "Users enrolled in this Cerebrate instance." - name: Organisations @@ -17,12 +19,88 @@ tags: description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." paths: + /api/v1/individuals/index: + get: + summary: "Get individuals list" + operationId: getIndividuals + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/IndividualListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/add: + post: + summary: "Add individual" + operationId: addIndividual + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/AddIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/edit/{individualId}: + put: + summary: "Edit individual" + operationId: editIndividual + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + requestBody: + $ref: "#/components/requestBodies/EditIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/delete/{individualId}: + delete: + summary: "Delete individual by ID" + operationId: deleteIndividualById + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/users/index: get: summary: "Get users list" operationId: getUsers tags: - Users + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/UserListResponse" @@ -185,6 +263,8 @@ paths: operationId: getOrganisations tags: - Organisations + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/OrganisationListResponse" @@ -195,6 +275,24 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/organisations/view/{organisationId}: + get: + summary: "View organisation by ID" + operationId: getOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/organisations/delete/{organisationId}: delete: summary: "Delete organisation by ID" @@ -259,6 +357,8 @@ paths: operationId: getTags tags: - Tags + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/TagListResponse" @@ -288,11 +388,64 @@ components: format: datetime example: "2022-01-05T11:19:26+00:00" + Email: + type: string + format: email + example: "user@example.com" + + # Individuals + IndividualFirstName: + type: string + example: "John" + + IndividualLastName: + type: string + example: "Doe" + + IndividualFullName: + type: string + example: "John Doe" + + IndividualPosition: + type: string + example: "Security Analyst" + + Individual: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/Email" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + $ref: "#/components/schemas/IndividualLastName" + full_name: + $ref: "#/components/schemas/IndividualFullName" + position: + $ref: "#/components/schemas/IndividualPosition" + tags: + $ref: "#/components/schemas/TagList" + aligments: + $ref: "#/components/schemas/AligmentList" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + # Users Username: type: string example: "admin" + IndividualList: + type: array + items: + $ref: "#/components/schemas/Individual" + User: type: object properties: @@ -320,8 +473,6 @@ components: items: $ref: "#/components/schemas/User" - # Individuals - # Organisations OrganisationName: type: string @@ -533,6 +684,14 @@ components: example: 404 parameters: + individualId: + name: userId + in: path + description: "Numeric ID of the User" + required: true + schema: + $ref: "#/components/schemas/ID" + userId: name: userId in: path @@ -549,6 +708,14 @@ components: schema: $ref: "#/components/schemas/ID" + quickFilter: + name: quickFilter + in: query + description: "Quick filter used to match multiple attributes such as name, description, emails, etc." + schema: + type: string + example: "user@example.com" + securitySchemes: ApiKeyAuth: type: apiKey @@ -560,6 +727,43 @@ components: Authorization: YOUR_API_KEY requestBodies: + # Individuals + AddIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/IndividualLastName" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + type: boolean + position: + $ref: "#/components/schemas/IndividualPosition" + + EditIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/IndividualLastName" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + type: boolean + position: + $ref: "#/components/schemas/IndividualPosition" + # Users AddUserRequest: required: true @@ -588,6 +792,8 @@ components: schema: type: object properties: + id: + $ref: "#/components/schemas/ID" individual_id: $ref: "#/components/schemas/ID" organisation_id: @@ -671,6 +877,21 @@ components: example: '["red"]' responses: + # Individuals + IndividualResponse: + description: "Individual response" + content: + application/json: + schema: + $ref: "#/components/schemas/Individual" + + IndividualListResponse: + description: "Individuals list response" + content: + application/json: + schema: + $ref: "#/components/schemas/IndividualList" + # Users UserResponse: description: "User response" From c8fd8f4a62674cd64d16019558bc37e9b34e76c9 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Thu, 13 Jan 2022 16:34:43 +0100 Subject: [PATCH 15/36] add: add basic api coverage of inbox processor endpoint, extend openapi spec --- tests/Fixture/InboxFixture.php | 61 ++++++ .../Api/Inbox/CreateInboxEntryApiTest.php | 82 +++++++ .../TestCase/Api/Inbox/IndexInboxApiTest.php | 46 ++++ webroot/docs/openapi.yaml | 203 +++++++++++++++++- 4 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 tests/Fixture/InboxFixture.php create mode 100644 tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php create mode 100644 tests/TestCase/Api/Inbox/IndexInboxApiTest.php diff --git a/tests/Fixture/InboxFixture.php b/tests/Fixture/InboxFixture.php new file mode 100644 index 0000000..40303b2 --- /dev/null +++ b/tests/Fixture/InboxFixture.php @@ -0,0 +1,61 @@ +records = [ + [ + 'id' => self::INBOX_USER_REGISTRATION_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'User', + 'action' => 'Registration', + 'title' => 'User account creation requested for foo@bar.com', + 'origin' => '::1', + 'comment' => null, + 'description' => 'Handle user account for this cerebrate instance', + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'data' => [ + 'email' => 'foo@bar.com', + 'password' => '$2y$10$dr5C0MWgBx1723yyws0HPudTqHz4k8wJ1PQ1ApVkNuH64LuZAr\/ve', + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INBOX_INCOMING_CONNECTION_REQUEST_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'LocalTool', + 'action' => 'IncomingConnectionRequest', + 'title' => 'Request for MISP Inter-connection', + 'origin' => 'http://127.0.0.1', + 'comment' => null, + 'description' => 'Handle Phase I of inter-connection when another cerebrate instance performs the request.', + 'user_id' => UsersFixture::USER_ORG_ADMIN_ID, + 'data' => [ + 'connectorName' => 'MispConnector', + 'cerebrateURL' => 'http://127.0.0.1', + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php new file mode 100644 index 0000000..30a44c2 --- /dev/null +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -0,0 +1,82 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddUserRegistrationInbox(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + // to avoid $this->request->clientIp() to return null + $_SERVER['REMOTE_ADDR'] = '::1'; + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists( + 'Inbox', + [ + 'id' => 3, // hacky, but `data` is json string cannot verify the value because of the hashed password + 'scope' => 'User', + 'action' => 'Registration', + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } + + public function testAddUserRegistrationInboxNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Inbox', ['id' => 3]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } +} diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php new file mode 100644 index 0000000..00c2cfd --- /dev/null +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -0,0 +1,46 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexInbox(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_USER_REGISTRATION_ID)); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_INCOMING_CONNECTION_REQUEST_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index d74ad68..59a0ecf 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -17,6 +17,8 @@ tags: description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." - name: Tags description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." + - name: Inbox + description: "Inbox messages represent A list of requests to be manually processed." paths: /api/v1/individuals/index: @@ -369,6 +371,42 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/inbox/index: + get: + summary: "Get inbox list" + operationId: getinbox + tags: + - Inbox + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/InboxListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/inbox/createEntry/User/Registration: + post: + summary: "Create user registration inbox entry" + operationId: createInboxEntry + tags: + - Inbox + requestBody: + $ref: "#/components/requestBodies/CreateUserRegistrationInboxEntryRequest" + responses: + "200": + $ref: "#/components/responses/CreateUserRegistrationInboxEntryResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -615,6 +653,109 @@ components: perm_org_admin: type: boolean + # Inbox + InboxScope: + type: string + enum: + - "User" + - "LocalTool" + + InboxAction: + type: string + enum: + - "Registration" + - "IncomingConnectionRequest" + - "AcceptedRequest" + - "DeclinedRequest" + + InboxTitle: + type: string + + InboxOrigin: + type: string + + InboxComment: + type: string + nullable: true + + InboxDescription: + type: string + nullable: true + + Inbox: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + scope: + $ref: "#/components/schemas/InboxScope" + action: + $ref: "#/components/schemas/InboxAction" + title: + $ref: "#/components/schemas/InboxTitle" + origin: + $ref: "#/components/schemas/InboxOrigin" + comment: + $ref: "#/components/schemas/InboxComment" + description: + $ref: "#/components/schemas/InboxDescription" + user_id: + $ref: "#/components/schemas/ID" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + UserRegistrationInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + email: + type: string + format: email + password: + type: string + user: + $ref: "#/components/schemas/User" + local_tool_connector_name: + type: string + nullable: true + + IncomingConnectionRequestInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + connectorName: + type: string + enum: + - "MispConnector" + cerebrateURL: + type: string + example: "http://192.168.0.1" + local_tool_id: + type: integer + remote_tool_id: + type: integer + + InboxList: + type: array + items: + anyOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - $ref: "#/components/schemas/IncomingConnectionRequestInbox" + # Errors ApiError: type: object @@ -685,7 +826,7 @@ components: parameters: individualId: - name: userId + name: individualId in: path description: "Numeric ID of the User" required: true @@ -876,6 +1017,20 @@ components: description: "Stringified JSON array of the tag names to remove." example: '["red"]' + # Inbox + CreateUserRegistrationInboxEntryRequest: + description: "Create user registration inbox entry request" + content: + application/json: + schema: + type: object + properties: + email: + type: string + format: email + password: + type: string + responses: # Individuals IndividualResponse: @@ -937,6 +1092,52 @@ components: schema: $ref: "#/components/schemas/TagList" + # Inbox + UserRegistrationInboxResponse: + description: "User registration inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/UserRegistrationInbox" + + IncomingConnectionRequestInboxResponse: + description: "Incoming connection request inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/IncomingConnectionRequestInbox" + + InboxListResponse: + description: "Inbox list response" + content: + application/json: + schema: + $ref: "#/components/schemas/InboxList" + + CreateUserRegistrationInboxEntryResponse: + description: "Inbox response" + content: + application/json: + schema: + type: object + properties: + data: + allOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - properties: + local_tool_connector_name: + type: string + nullable: true + success: + type: boolean + message: + type: string + example: "User account creation requested. Please wait for an admin to approve your account." + errors: + type: array + items: + type: object + # Errors ApiErrorResponse: description: "Unexpected API error" From 2d05f9228dcfc4021c610dcd493c408e9b0bfe6b Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Thu, 13 Jan 2022 16:57:05 +0100 Subject: [PATCH 16/36] add: some extra scopes and actions --- webroot/docs/openapi.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 59a0ecf..8cd48f1 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -659,6 +659,9 @@ components: enum: - "User" - "LocalTool" + - "Brood" + - "Proposal" + - "Synchronisation" InboxAction: type: string @@ -667,6 +670,10 @@ components: - "IncomingConnectionRequest" - "AcceptedRequest" - "DeclinedRequest" + - "Synchronisation" + - "OneWaySynchronization" + - "ProposalEdit" + - "DataExchange" InboxTitle: type: string From fa7316db3f0b5f1dc68be3ae934673ca3c54cd7e Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 14 Jan 2022 14:43:21 +0100 Subject: [PATCH 17/36] add: add sharing groups api tests, extend openapi spec --- tests/Fixture/SharingGroupsFixture.php | 50 +++++ .../Api/Individuals/ViewIndividualApiTest.php | 45 ++++ .../DeleteSharingGroupApiTest.php | 60 ++++++ .../SharingGroups/EditSharingGroupApiTest.php | 83 ++++++++ .../IndexSharingGroupsApiTest.php | 46 +++++ .../SharingGroups/ViewSharingGroupApiTest.php | 46 +++++ webroot/docs/openapi.yaml | 194 +++++++++++++++++- 7 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 tests/Fixture/SharingGroupsFixture.php create mode 100644 tests/TestCase/Api/Individuals/ViewIndividualApiTest.php create mode 100644 tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php create mode 100644 tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php create mode 100644 tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php create mode 100644 tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php diff --git a/tests/Fixture/SharingGroupsFixture.php b/tests/Fixture/SharingGroupsFixture.php new file mode 100644 index 0000000..e12e021 --- /dev/null +++ b/tests/Fixture/SharingGroupsFixture.php @@ -0,0 +1,50 @@ +records = [ + [ + 'id' => self::SHARING_GROUP_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Sharing Group A', + 'releasability' => 'Sharing Group A', + 'description' => 'Sharing Group A description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::SHARING_GROUP_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Sharing Group B', + 'releasability' => 'Sharing Group B', + 'description' => 'Sharing Group B description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php new file mode 100644 index 0000000..4a0d4a4 --- /dev/null +++ b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php @@ -0,0 +1,45 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewIndividualById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php new file mode 100644 index 0000000..8b49f3b --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -0,0 +1,60 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteSharingGroup(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php new file mode 100644 index 0000000..0cf6f05 --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php @@ -0,0 +1,83 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditSharingGroup(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Sharing Group 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'SharingGroups', + [ + 'id' => SharingGroupsFixture::SHARING_GROUP_A_ID, + 'name' => 'Test Sharing Group 4321', + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Sharing Group 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'SharingGroups', + [ + 'id' => SharingGroupsFixture::SHARING_GROUP_B_ID, + 'name' => 'Test Sharing Group 1234' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php new file mode 100644 index 0000000..85779aa --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php @@ -0,0 +1,46 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexSharingGroups(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_B_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php new file mode 100644 index 0000000..5f5e6d1 --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -0,0 +1,46 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testVieSharingGroupById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "Sharing Group A"'); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 8cd48f1..c8a8c70 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -19,6 +19,8 @@ tags: description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." - name: Inbox description: "Inbox messages represent A list of requests to be manually processed." + - name: SharingGroups + description: "Sharing groups are distribution lists usable by tools that can exchange information with a list of trusted partners. Create recurring or ad hoc sharing groups and share them with the members of the sharing group." paths: /api/v1/individuals/index: @@ -39,6 +41,24 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/individuals/view/{individualId}: + get: + summary: "Get individual by ID" + operationId: getIndividualById + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/individuals/add: post: summary: "Add individual" @@ -131,7 +151,7 @@ paths: /api/v1/users/view/{userId}: get: - summary: "Get information of a user by id" + summary: "Get information of a user by ID" operationId: viewUserById tags: - Users @@ -407,6 +427,80 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/sharingGroups/index: + get: + summary: "Get a sharing groups list" + operationId: getSharingGroups + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/SharingGroupListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/sharingGroups/view/{sharingGroupId}: + get: + summary: "Get sharing group by ID" + operationId: getSharingGroupById + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/sharingGroups/delete/{sharingGroupId}: + delete: + summary: "Delete sharing group by ID" + operationId: deleteSharingGroupById + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/sharingGroups/edit/{sharingGroupId}: + put: + summary: "Edit sharing group" + operationId: editSharingGroup + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + requestBody: + $ref: "#/components/requestBodies/EditSharingGroupRequest" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -763,6 +857,55 @@ components: - $ref: "#/components/schemas/UserRegistrationInbox" - $ref: "#/components/schemas/IncomingConnectionRequestInbox" + # SharingGroups + SharingGroupName: + type: string + + SharingGroupReleasability: + type: string + + SharingGroupDescription: + type: string + + SharingGroup: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + sharing_group_orgs: + type: array + items: + $ref: "#/components/schemas/Organisation" + user: + $ref: "#/components/schemas/User" + organisation: + $ref: "#/components/schemas/Organisation" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + SharingGroupList: + type: array + items: + $ref: "#/components/schemas/SharingGroup" + # Errors ApiError: type: object @@ -856,6 +999,14 @@ components: schema: $ref: "#/components/schemas/ID" + sharingGroupId: + name: sharingGroupId + in: path + description: "Numeric ID of the Sharing Group" + required: true + schema: + $ref: "#/components/schemas/ID" + quickFilter: name: quickFilter in: query @@ -1038,6 +1189,31 @@ components: password: type: string + # SharingGroups + EditSharingGroupRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + responses: # Individuals IndividualResponse: @@ -1144,6 +1320,22 @@ components: type: array items: type: object + # TODO: describe + + # SharingGroups + SharingGroupResponse: + description: "Sharing group response" + content: + application/json: + schema: + $ref: "#/components/schemas/SharingGroup" + + SharingGroupListResponse: + description: "Sharing groups list response" + content: + application/json: + schema: + $ref: "#/components/schemas/SharingGroupList" # Errors ApiErrorResponse: From 25ded7e3bfa4e970fd8bc07177ff92f32dedcd9b Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Fri, 14 Jan 2022 17:43:53 +0100 Subject: [PATCH 18/36] add: more sharing groups api tests, add broods api tests, extend openapi spec --- tests/Fixture/BroodsFixture.php | 55 ++++ tests/Fixture/OrganisationsFixture.php | 1 - tests/Fixture/SharingGroupsFixture.php | 4 +- tests/TestCase/Api/Broods/AddBroodApiTest.php | 91 ++++++ .../Api/Broods/DeleteBroodsApiTest.php | 60 ++++ .../TestCase/Api/Broods/EditBroodApiTest.php | 83 ++++++ .../Api/Broods/IndexBroodsApiTest.php | 45 +++ .../TestCase/Api/Broods/ViewBroodApiTest.php | 46 +++ .../SharingGroups/AddSharingGroupApiTest.php | 90 ++++++ .../SharingGroups/ViewSharingGroupApiTest.php | 4 +- tests/bootstrap.php | 3 +- webroot/docs/openapi.yaml | 280 +++++++++++++++++- 12 files changed, 750 insertions(+), 12 deletions(-) create mode 100644 tests/Fixture/BroodsFixture.php create mode 100644 tests/TestCase/Api/Broods/AddBroodApiTest.php create mode 100644 tests/TestCase/Api/Broods/DeleteBroodsApiTest.php create mode 100644 tests/TestCase/Api/Broods/EditBroodApiTest.php create mode 100644 tests/TestCase/Api/Broods/IndexBroodsApiTest.php create mode 100644 tests/TestCase/Api/Broods/ViewBroodApiTest.php create mode 100644 tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php diff --git a/tests/Fixture/BroodsFixture.php b/tests/Fixture/BroodsFixture.php new file mode 100644 index 0000000..adc035c --- /dev/null +++ b/tests/Fixture/BroodsFixture.php @@ -0,0 +1,55 @@ +records = [ + [ + 'id' => self::BROOD_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_A_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::BROOD_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_B_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php index 7b7439e..8531d0c 100644 --- a/tests/Fixture/OrganisationsFixture.php +++ b/tests/Fixture/OrganisationsFixture.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Test\Fixture; use Cake\TestSuite\Fixture\TestFixture; -use Authentication\PasswordHasher\DefaultPasswordHasher; class OrganisationsFixture extends TestFixture { diff --git a/tests/Fixture/SharingGroupsFixture.php b/tests/Fixture/SharingGroupsFixture.php index e12e021..79665b6 100644 --- a/tests/Fixture/SharingGroupsFixture.php +++ b/tests/Fixture/SharingGroupsFixture.php @@ -22,7 +22,7 @@ class SharingGroupsFixture extends TestFixture 'id' => self::SHARING_GROUP_A_ID, 'uuid' => $faker->uuid(), 'name' => 'Sharing Group A', - 'releasability' => 'Sharing Group A', + 'releasability' => 'Sharing Group A releasability', 'description' => 'Sharing Group A description', 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, 'user_id' => UsersFixture::USER_ADMIN_ID, @@ -35,7 +35,7 @@ class SharingGroupsFixture extends TestFixture 'id' => self::SHARING_GROUP_B_ID, 'uuid' => $faker->uuid(), 'name' => 'Sharing Group B', - 'releasability' => 'Sharing Group B', + 'releasability' => 'Sharing Group B releasability', 'description' => 'Sharing Group B description', 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, 'user_id' => UsersFixture::USER_ADMIN_ID, diff --git a/tests/TestCase/Api/Broods/AddBroodApiTest.php b/tests/TestCase/Api/Broods/AddBroodApiTest.php new file mode 100644 index 0000000..099f3bd --- /dev/null +++ b/tests/TestCase/Api/Broods/AddBroodApiTest.php @@ -0,0 +1,91 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddBrood(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => $faker->sha1, + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('Broods', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => $faker->sha1, + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Broods', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php new file mode 100644 index 0000000..1b8d2ce --- /dev/null +++ b/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php @@ -0,0 +1,60 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteBrood(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/Broods/EditBroodApiTest.php b/tests/TestCase/Api/Broods/EditBroodApiTest.php new file mode 100644 index 0000000..bd15a39 --- /dev/null +++ b/tests/TestCase/Api/Broods/EditBroodApiTest.php @@ -0,0 +1,83 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditBrood(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Brood 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Broods', + [ + 'id' => BroodsFixture::BROOD_A_ID, + 'name' => 'Test Brood 4321', + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Brood 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Broods', + [ + 'id' => BroodsFixture::BROOD_B_ID, + 'name' => 'Test Brood 1234' + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php new file mode 100644 index 0000000..97d0df8 --- /dev/null +++ b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php @@ -0,0 +1,45 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexBroods(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Broods/ViewBroodApiTest.php b/tests/TestCase/Api/Broods/ViewBroodApiTest.php new file mode 100644 index 0000000..b3f520d --- /dev/null +++ b/tests/TestCase/Api/Broods/ViewBroodApiTest.php @@ -0,0 +1,46 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewBroodGroupById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php new file mode 100644 index 0000000..45a79aa --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php @@ -0,0 +1,90 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddSharingGroup(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Test Sharing Group', + 'releasability' => 'Test Sharing Group releasability', + 'description' => 'Test Sharing Group description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('SharingGroups', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Test Sharing Group', + 'releasability' => 'Sharing Group A', + 'description' => 'Sharing Group A description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('SharingGroups', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php index 5f5e6d1..0e86fc7 100644 --- a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -32,14 +32,14 @@ class ViewSharingGroupApiTest extends TestCase $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); } - public function testVieSharingGroupById(): void + public function testViewSharingGroupById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); $this->get($url); $this->assertResponseOk(); - $this->assertResponseContains('"name": "Sharing Group A"'); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); // TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6df9c27..c9a8b8c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -56,6 +56,7 @@ session_id('cli'); // hacky way to skip migrations if (!in_array('skip-migrations', $_SERVER['argv'])) { + echo "[ * ] Running DB migrations, it may take some time ...\n"; $migrator = new Migrator(); $migrator->runMany([ ['connection' => 'test'], @@ -63,5 +64,5 @@ if (!in_array('skip-migrations', $_SERVER['argv'])) { ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test'] ]); } else { - echo "[ * ] Skipping migrations ...\n"; + echo "[ * ] Skipping DB migrations ...\n"; } diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index c8a8c70..81ac9aa 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -21,6 +21,8 @@ tags: description: "Inbox messages represent A list of requests to be manually processed." - name: SharingGroups description: "Sharing groups are distribution lists usable by tools that can exchange information with a list of trusted partners. Create recurring or ad hoc sharing groups and share them with the members of the sharing group." + - name: Broods + description: "Cerebrate can connect to other Cerebrate instances to exchange trust information and to instrument interconnectivity between connected local tools. Each such Cerebrate instance with its connected tools is considered to be a brood." paths: /api/v1/individuals/index: @@ -66,7 +68,7 @@ paths: tags: - Users requestBody: - $ref: "#/components/requestBodies/AddIndividualRequest" + $ref: "#/components/requestBodies/CreateIndividualRequest" responses: "200": $ref: "#/components/responses/IndividualResponse" @@ -174,7 +176,7 @@ paths: tags: - Users requestBody: - $ref: "#/components/requestBodies/AddUserRequest" + $ref: "#/components/requestBodies/CreateUserRequest" responses: "200": $ref: "#/components/responses/UserResponse" @@ -248,7 +250,7 @@ paths: tags: - Organisations requestBody: - $ref: "#/components/requestBodies/AddOrganisationRequest" + $ref: "#/components/requestBodies/CreateOrganisationRequest" responses: "200": $ref: "#/components/responses/OrganisationResponse" @@ -445,6 +447,24 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/sharingGroups/add: + post: + summary: "Add sharing group" + operationId: addSharingGroup + tags: + - SharingGroups + requestBody: + $ref: "#/components/requestBodies/CreateSharingGroupRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/sharingGroups/view/{sharingGroupId}: get: summary: "Get sharing group by ID" @@ -501,6 +521,98 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/broods/index: + get: + summary: "Get broods list" + operationId: getBroods + tags: + - Broods + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/BroodListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/broods/view/{broodId}: + get: + summary: "Get brood by ID" + operationId: getBroodById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/broods/add: + post: + summary: "Add brood" + operationId: addBrood + tags: + - Broods + requestBody: + $ref: "#/components/requestBodies/CreateBroodRequest" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/broods/edit/{sharingGroupId}: + put: + summary: "Edit brood" + operationId: editBrood + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + requestBody: + $ref: "#/components/requestBodies/EditBroodRequest" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/broods/delete/{broodId}: + delete: + summary: "Delete brood by ID" + operationId: deleteBroodById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -525,6 +637,9 @@ components: format: email example: "user@example.com" + AuthKey: + type: string + # Individuals IndividualFirstName: type: string @@ -906,6 +1021,59 @@ components: items: $ref: "#/components/schemas/SharingGroup" + # Broods + BroodName: + type: string + + BroodDescription: + type: string + + BroodUrl: + type: string + + BroodIsTrusted: + type: boolean + description: "Trusted upstream source" + + BroodIsPull: + type: boolean + description: "Enable pulling of trust information" + + Brood: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKey" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + organisation: + $ref: "#/components/schemas/Organisation" + + BroodList: + type: array + items: + $ref: "#/components/schemas/Brood" + # Errors ApiError: type: object @@ -1007,6 +1175,14 @@ components: schema: $ref: "#/components/schemas/ID" + broodId: + name: broodId + in: path + description: "Numeric ID of the Brood" + required: true + schema: + $ref: "#/components/schemas/ID" + quickFilter: name: quickFilter in: query @@ -1027,7 +1203,7 @@ components: requestBodies: # Individuals - AddIndividualRequest: + CreateIndividualRequest: required: true content: application/json: @@ -1064,7 +1240,7 @@ components: $ref: "#/components/schemas/IndividualPosition" # Users - AddUserRequest: + CreateUserRequest: required: true content: application/json: @@ -1107,7 +1283,7 @@ components: type: string # Organisations - AddOrganisationRequest: + CreateOrganisationRequest: required: true content: application/json: @@ -1190,6 +1366,30 @@ components: type: string # SharingGroups + CreateSharingGroupRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + EditSharingGroupRequest: required: true content: @@ -1214,6 +1414,59 @@ components: local: type: boolean + # Broods + CreateBroodRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKey" + + EditBroodRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKey" + responses: # Individuals IndividualResponse: @@ -1337,6 +1590,21 @@ components: schema: $ref: "#/components/schemas/SharingGroupList" + # Broods + BroodResponse: + description: "Brood response" + content: + application/json: + schema: + $ref: "#/components/schemas/Brood" + + BroodListResponse: + description: "Brood list response" + content: + application/json: + schema: + $ref: "#/components/schemas/BroodList" + # Errors ApiErrorResponse: description: "Unexpected API error" From 299cb126dc664794f8fab314aaddf91ca56e383a Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 17 Jan 2022 16:02:15 +0100 Subject: [PATCH 19/36] add: wiremock tests and boilerplate, update test readme, extend openapi spec --- .gitignore | 1 + composer.json | 11 ++- phpunit.xml.dist | 17 +++-- tests/Fixture/BroodsFixture.php | 17 +++++ tests/Helper/ApiTestTrait.php | 8 ++- tests/Helper/WireMockTestTrait.php | 49 +++++++++++++ tests/Helper/wiremock/start.sh | 38 ++++++++++ tests/Helper/wiremock/stop.sh | 23 ++++++ tests/README.md | 70 +++++++++++++++--- tests/TestCase/Api/Broods/AddBroodApiTest.php | 6 -- .../Api/Broods/DeleteBroodsApiTest.php | 6 -- .../TestCase/Api/Broods/EditBroodApiTest.php | 6 -- .../Api/Broods/IndexBroodsApiTest.php | 6 -- .../Api/Broods/TestBroodConnectionApiTest.php | 72 +++++++++++++++++++ .../TestCase/Api/Broods/ViewBroodApiTest.php | 6 -- .../Api/Inbox/CreateInboxEntryApiTest.php | 6 -- .../TestCase/Api/Inbox/IndexInboxApiTest.php | 6 -- .../Api/Individuals/AddIndividualApiTest.php | 6 -- .../Individuals/DeleteIndividualApiTest.php | 6 -- .../Api/Individuals/EditIndividualApiTest.php | 6 -- .../Individuals/IndexIndividualsApiTest.php | 6 -- .../Api/Individuals/ViewIndividualApiTest.php | 6 -- .../Organisations/AddOrganisationApiTest.php | 6 -- .../DeleteOrganisationApiTest.php | 6 -- .../Organisations/EditOrganisationApiTest.php | 6 -- .../IndexOrganisationsApiTest.php | 6 -- .../Organisations/TagOrganisationApiTest.php | 6 -- .../UntagOrganisationApiTest.php | 6 -- .../Organisations/ViewOrganisationApiTest.php | 6 -- .../SharingGroups/AddSharingGroupApiTest.php | 6 -- .../DeleteSharingGroupApiTest.php | 6 -- .../SharingGroups/EditSharingGroupApiTest.php | 6 -- .../IndexSharingGroupsApiTest.php | 6 -- .../SharingGroups/ViewSharingGroupApiTest.php | 6 -- tests/TestCase/Api/Tags/IndexTagsApiTest.php | 6 -- tests/TestCase/Api/Users/AddUserApiTest.php | 6 -- .../TestCase/Api/Users/DeleteUserApiTest.php | 6 -- tests/TestCase/Api/Users/EditUserApiTest.php | 6 -- .../TestCase/Api/Users/IndexUsersApiTest.php | 6 -- tests/TestCase/Api/Users/ViewUserApiTest.php | 6 -- tests/bootstrap.php | 4 +- webroot/docs/openapi.yaml | 54 ++++++++++++++ 42 files changed, 340 insertions(+), 204 deletions(-) create mode 100644 tests/Helper/WireMockTestTrait.php create mode 100644 tests/Helper/wiremock/start.sh create mode 100644 tests/Helper/wiremock/stop.sh create mode 100644 tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php diff --git a/.gitignore b/.gitignore index ba05ac6..ce8fde9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ webroot/theme/node_modules docker/run/ .phpunit.result.cache config.json +phpunit.xml diff --git a/composer.json b/composer.json index 4e1af85..f51bc6f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "josegonzalez/dotenv": "^3.2", "league/openapi-psr7-validator": "^0.16.4", "phpunit/phpunit": "^8.5", - "psy/psysh": "@stable" + "psy/psysh": "@stable", + "wiremock-php/wiremock-php": "^2.32" }, "suggest": { "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.", @@ -53,11 +54,15 @@ "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", "stan": "phpstan analyse src/", - "test": "phpunit --colors=always" + "test": [ + "sh ./tests/Helper/wiremock/start.sh", + "phpunit", + "sh ./tests/Helper/wiremock/stop.sh" + ] }, "prefer-stable": true, "config": { "sort-packages": true }, "minimum-stability": "dev" -} +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7be6529..403e1e5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,12 @@ - + - - + + + + + + @@ -37,4 +36,4 @@ - + \ No newline at end of file diff --git a/tests/Fixture/BroodsFixture.php b/tests/Fixture/BroodsFixture.php index adc035c..c329e6a 100644 --- a/tests/Fixture/BroodsFixture.php +++ b/tests/Fixture/BroodsFixture.php @@ -16,6 +16,9 @@ class BroodsFixture extends TestFixture public const BROOD_B_ID = 2; public const BROOD_B_API_KEY = 'ae4f281df5a5d0ff3cad6371f76d5c29b6d953ec'; + public const BROOD_WIREMOCK_ID = 3; + public const BROOD_WIREMOCK_API_KEY = 'bfc63c07f74fa18b52d3cced97072cad00e51346'; + public function init(): void { $faker = \Faker\Factory::create(); @@ -48,6 +51,20 @@ class BroodsFixture extends TestFixture 'authkey' => self::BROOD_B_API_KEY, 'created' => $faker->dateTime()->getTimestamp(), 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::BROOD_WIREMOCK_ID, + 'uuid' => $faker->uuid(), + 'name' => 'wiremock', + 'url' => 'http://localhost:8080', + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_WIREMOCK_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() ] ]; parent::init(); diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 21b0c8b..8e20a36 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -24,6 +24,12 @@ trait ApiTestTrait /** @var ResponseValidator */ private $responseValidator; + public function setUp(): void + { + parent::setUp(); + $this->initializeOpenApiValidator($_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'); + } + public function setAuthToken(string $authToken): void { $this->_authToken = $authToken; @@ -51,7 +57,7 @@ trait ApiTestTrait * @param string $specFile * @return void */ - public function initializeValidator(string $specFile): void + public function initializeOpenApiValidator(string $specFile): void { $this->validator = (new ValidatorBuilder)->fromYamlFile($specFile); $this->requestValidator = $this->validator->getRequestValidator(); diff --git a/tests/Helper/WireMockTestTrait.php b/tests/Helper/WireMockTestTrait.php new file mode 100644 index 0000000..9b42f7e --- /dev/null +++ b/tests/Helper/WireMockTestTrait.php @@ -0,0 +1,49 @@ + */ + private $config = [ + 'hostname' => 'localhost', + 'port' => 8080 + ]; + + public function initializeWireMock(): void + { + $this->wiremock = WireMock::create( + $_ENV['WIREMOCK_HOST'] ?? $this->config['hostname'], + $_ENV['WIREMOCK_PORT'] ?? $this->config['port'] + ); + + if (!$this->wiremock->isAlive()) { + throw new Exception('Failed to connect to WireMock server.'); + } + + $this->clearWireMockStubs(); + } + + public function clearWireMockStubs(): void + { + $this->wiremock->resetToDefault(); + } + + public function getWireMock(): WireMock + { + return $this->wiremock; + } + + public function getWireMockBaseUrl(): string + { + return sprintf('http://%s:%s', $this->config['hostname'], $this->config['port']); + } +} diff --git a/tests/Helper/wiremock/start.sh b/tests/Helper/wiremock/start.sh new file mode 100644 index 0000000..60b8197 --- /dev/null +++ b/tests/Helper/wiremock/start.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Adapted from @rowanhill wiremock start.sh script +# https://github.com/rowanhill/wiremock-php/blob/master/wiremock/start.sh + +cd ./tmp/ + +instance=1 +port=8080 +if [ $# -gt 0 ]; then + instance=$1 + port=$2 +fi +pidFile=wiremock.$instance.pid +logFile=wiremock.$instance.log + +# Ensure WireMock isn't already running +if [ -e $pidFile ]; then + echo WireMock is already started: see process `cat $pidFile` 1>&2 + exit 0 +fi + +# Download the wiremock jar if we need it +if ! [ -e wiremock-standalone.jar ]; then + echo WireMock standalone JAR missing. Downloading. + curl https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-jre8-standalone/2.32.0/wiremock-jre8-standalone-2.32.0.jar -o wiremock-standalone.jar + status=$? + if [ ${status} -ne 0 ]; then + echo curl could not download WireMock JAR 1>&2 + exit ${status} + fi +fi + +# Start WireMock in standalone mode (in a background process) and save its output to a log +java -jar wiremock-standalone.jar --port $port --root-dir $instance --disable-banner &> $logFile 2>&1 & +pgrep -f wiremock-standalone.jar > $pidFile + +echo WireMock $instance started on port $port \ No newline at end of file diff --git a/tests/Helper/wiremock/stop.sh b/tests/Helper/wiremock/stop.sh new file mode 100644 index 0000000..f9e3f9e --- /dev/null +++ b/tests/Helper/wiremock/stop.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Adapted from @rowanhill wiremock stop.sh script +# https://github.com/rowanhill/wiremock-php/blob/master/wiremock/stop.sh + +cd ./tmp/ + +instance=1 +if [ $# -gt 0 ]; then + instance=$1 +fi +pidFile=wiremock.$instance.pid + + +if [ -e $pidFile ]; then + kill -9 `cat $pidFile` + rm $pidFile +else + echo WireMock is not started 2>&1 + exit 1 +fi + +echo WireMock $instance stopped \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 0486b3b..e080442 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,5 @@ # Testing -## Configuration -1. Add a `cerebrate_test` database to the db: +1. Add a `cerebrate_test` database to the database: ```mysql CREATE DATABASE cerebrate_test; GRANT ALL PRIVILEGES ON cerebrate_test.* to cerebrate@localhost; @@ -28,12 +27,24 @@ QUIT; ``` ## Runing the tests - ``` $ composer install -$ vendor/bin/phpunit +$ composer test +> sh ./tests/Helper/wiremock/start.sh +WireMock 1 started on port 8080 +> phpunit +[ * ] Running DB migrations, it may take some time ... + +The WireMock server is started ..... +port: 8080 +enable-browser-proxying: false +disable-banner: true +no-request-journal: false +verbose: false + PHPUnit 8.5.22 by Sebastian Bergmann and contributors. + ..... 5 / 5 (100%) Time: 11.61 seconds, Memory: 26.00 MB @@ -51,12 +62,35 @@ Available suites: * `controller`: runs only controller tests * _to be continued ..._ -By default the database is re-generated before running the test suite, to skip this step and speed up the test run use the `-d skip-migrations` option: -``` -$ vendor/bin/phpunit -d skip-migrations +By default the database is re-generated before running the test suite, to skip this step and speed up the test run set the following env variable in `phpunit.xml`: +```xml + + ... + + ``` +## Extras +### WireMock +Some integration tests perform calls to external APIs, we use WireMock to mock the response of these API calls. -## Coverage +To download and run WireMock run the following script in a separate terminal: + ``` + sh ./tests/Helper/wiremock/start.sh + ``` + +You can also run WireMock with docker, check the official docs: http://wiremock.org/docs/docker/ + +> NOTE: When running the tests with `composer test` WireMock is automatically started and stoped after the tests finish. + +The default `hostname` and `port` for WireMock are set in `phpunit.xml` as environment variables: +```xml + + ... + + + +``` +### Coverage HTML: ``` $ vendor/bin/phpunit --coverage-html tmp/coverage @@ -66,3 +100,23 @@ XML: ``` $ vendor/bin/phpunit --verbose --coverage-clover=coverage.xml ``` + +### OpenAPI validation +API tests can assert the API response matches the OpenAPI specification, after the request add this line: + +```php +$this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); +``` + +The default OpenAPI spec path is set in `phpunit.xml` as a environment variablea: +```xml + + ... + + +``` + +## TODO +- [ ] Validate API requests against the OpenAPI spec +- [ ] Validate response content matches / implement _seeResponseContainsJson()_ or equivalent +- [ ] Parse OpenAPI spec only once per test run, currently re-loading in every _TestCase::setUp()_ \ No newline at end of file diff --git a/tests/TestCase/Api/Broods/AddBroodApiTest.php b/tests/TestCase/Api/Broods/AddBroodApiTest.php index 099f3bd..14fb2c5 100644 --- a/tests/TestCase/Api/Broods/AddBroodApiTest.php +++ b/tests/TestCase/Api/Broods/AddBroodApiTest.php @@ -26,12 +26,6 @@ class AddBroodApiTest extends TestCase 'app.Broods' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddBrood(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php index 1b8d2ce..94aa0a8 100644 --- a/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php +++ b/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php @@ -26,12 +26,6 @@ class DeleteBroodsApiTest extends TestCase 'app.Broods' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testDeleteBrood(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Broods/EditBroodApiTest.php b/tests/TestCase/Api/Broods/EditBroodApiTest.php index bd15a39..d8fa01f 100644 --- a/tests/TestCase/Api/Broods/EditBroodApiTest.php +++ b/tests/TestCase/Api/Broods/EditBroodApiTest.php @@ -27,12 +27,6 @@ class EditBroodApiTest extends TestCase 'app.Broods' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testEditBrood(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php index 97d0df8..856b151 100644 --- a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php +++ b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php @@ -26,12 +26,6 @@ class IndexBroodsApiTest extends TestCase 'app.Broods' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexBroods(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php new file mode 100644 index 0000000..1084131 --- /dev/null +++ b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php @@ -0,0 +1,72 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->initializeWireMock(); + $this->mockCerebrateStatusResponse(); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_WIREMOCK_ID); + $this->get($url); + + $this->getWireMock()->verify( + WireMock::getRequestedFor(WireMock::urlEqualTo('/instance/status.json')) + ->withHeader('Content-Type', WireMock::equalTo('application/json')) + ->withHeader('Authorization', WireMock::equalTo(BroodsFixture::BROOD_WIREMOCK_API_KEY)) + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"user": "wiremock"'); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } + + private function mockCerebrateStatusResponse(): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo('/instance/status.json')) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode([ + "version" => "0.1", + "application" => "Cerebrate", + "user" => [ + "id" => 1, + "username" => "wiremock", + "role" => [ + "id" => 1 + ] + ] + ]))) + ); + } +} diff --git a/tests/TestCase/Api/Broods/ViewBroodApiTest.php b/tests/TestCase/Api/Broods/ViewBroodApiTest.php index b3f520d..35bb957 100644 --- a/tests/TestCase/Api/Broods/ViewBroodApiTest.php +++ b/tests/TestCase/Api/Broods/ViewBroodApiTest.php @@ -26,12 +26,6 @@ class ViewBroodApiTest extends TestCase 'app.Broods' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testViewBroodGroupById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php index 30a44c2..39879f3 100644 --- a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -25,12 +25,6 @@ class CreateInboxEntryApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddUserRegistrationInbox(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php index 00c2cfd..46f1b92 100644 --- a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -26,12 +26,6 @@ class IndexInboxApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexInbox(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php index 64855e8..0320c30 100644 --- a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -24,12 +24,6 @@ class AddIndividualApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddIndividual(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php index c493c4b..e50cf63 100644 --- a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -25,12 +25,6 @@ class DeleteIndividualApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testDeleteIndividual(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php index fcef7fd..642482c 100644 --- a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -25,12 +25,6 @@ class EditIndividualApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testEditIndividualAsAdmin(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php index 55f6be1..d8254d1 100644 --- a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -25,12 +25,6 @@ class IndexIndividualsApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexIndividuals(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php index 4a0d4a4..5d47610 100644 --- a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php @@ -25,12 +25,6 @@ class ViewIndividualApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testViewIndividualById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php index a3f2585..d561292 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -24,12 +24,6 @@ class AddOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddOrganisation(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index e16f57b..6a323fb 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -25,12 +25,6 @@ class DeleteOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testDeleteOrganisation(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php index d9cc7c6..75c032c 100644 --- a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -25,12 +25,6 @@ class EditOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testEditOrganisation(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php index ba7c255..709883a 100644 --- a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -25,12 +25,6 @@ class IndexOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexOrganisations(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php index ed74c74..c79109a 100644 --- a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -28,12 +28,6 @@ class TagOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testTagOrganisation(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php index c8878d6..1aff58a 100644 --- a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -28,12 +28,6 @@ class UntagOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testUntagOrganisation(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php index 630fd92..7170e98 100644 --- a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -28,12 +28,6 @@ class ViewOrganisationApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testViewOrganisationById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php index 45a79aa..0b0c8ab 100644 --- a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php @@ -27,12 +27,6 @@ class AddSharingGroupApiTest extends TestCase 'app.SharingGroups' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddSharingGroup(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php index 8b49f3b..c93bb05 100644 --- a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -26,12 +26,6 @@ class DeleteSharingGroupApiTest extends TestCase 'app.SharingGroups' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testDeleteSharingGroup(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php index 0cf6f05..7de711c 100644 --- a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php @@ -27,12 +27,6 @@ class EditSharingGroupApiTest extends TestCase 'app.SharingGroups' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testEditSharingGroup(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php index 85779aa..82f7255 100644 --- a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php +++ b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php @@ -26,12 +26,6 @@ class IndexSharingGroupsApiTest extends TestCase 'app.SharingGroups' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexSharingGroups(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php index 0e86fc7..3944122 100644 --- a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -26,12 +26,6 @@ class ViewSharingGroupApiTest extends TestCase 'app.SharingGroups' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testViewSharingGroupById(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Tags/IndexTagsApiTest.php b/tests/TestCase/Api/Tags/IndexTagsApiTest.php index 137151f..330e93f 100644 --- a/tests/TestCase/Api/Tags/IndexTagsApiTest.php +++ b/tests/TestCase/Api/Tags/IndexTagsApiTest.php @@ -25,12 +25,6 @@ class IndexTagsApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexTags(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index e60cf3a..396307e 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -27,12 +27,6 @@ class AddUserApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testAddUser(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 69bd87c..50df2e5 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -27,12 +27,6 @@ class DeleteUserApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testDeleteUser(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index a8afa11..8d2810d 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -26,12 +26,6 @@ class EditUserApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testEditUser(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php index 403a046..7d96936 100644 --- a/tests/TestCase/Api/Users/IndexUsersApiTest.php +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -25,12 +25,6 @@ class IndexUsersApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testIndexUsers(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php index 99bdafe..d1d2c54 100644 --- a/tests/TestCase/Api/Users/ViewUserApiTest.php +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -25,12 +25,6 @@ class ViewUserApiTest extends TestCase 'app.AuthKeys' ]; - public function setUp(): void - { - parent::setUp(); - $this->initializeValidator(APP . '../webroot/docs/openapi.yaml'); - } - public function testViewMyUser(): void { $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c9a8b8c..34939c3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -18,7 +18,6 @@ declare(strict_types=1); use Cake\Core\Configure; use Cake\Datasource\ConnectionManager; -use Cake\TestSuite\Fixture\SchemaLoader; use Migrations\TestSuite\Migrator; /** @@ -54,8 +53,7 @@ ConnectionManager::alias('test_debug_kit', 'debug_kit'); // has been written to. session_id('cli'); -// hacky way to skip migrations -if (!in_array('skip-migrations', $_SERVER['argv'])) { +if (!$_ENV['SKIP_DB_MIGRATIONS']) { echo "[ * ] Running DB migrations, it may take some time ...\n"; $migrator = new Migrator(); $migrator->runMany([ diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 81ac9aa..f9418b9 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -613,6 +613,24 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/broods/testConnection/{broodId}: + get: + summary: "Test brood connection by ID" + operationId: testBroodConnectionById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/TestBroodConnectionResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -714,6 +732,15 @@ components: $ref: "#/components/schemas/DateTime" organisation_id: $ref: "#/components/schemas/ID" + organisation: + $ref: "#/components/schemas/Organisation" + individual: + $ref: "#/components/schemas/Individual" + role: + $ref: "#/components/schemas/Role" + # user_settings: TODO + # user_settings_by_name: TODO + # user_settings_by_name_with_fallback: TODO UserList: type: array @@ -1605,6 +1632,33 @@ components: schema: $ref: "#/components/schemas/BroodList" + TestBroodConnectionResponse: + description: "Brood list response" + content: + application/json: + schema: + type: object + properties: + code: + type: integer + description: "HTTP status code" + example: 200 + response: + type: object + properties: + version: + type: string + example: "0.1" + application: + type: string + example: "Cerebrate" + user: + type: string + example: "sync" + ping: + type: number + format: float + # Errors ApiErrorResponse: description: "Unexpected API error" From b1ad454db8eac8c639569af157ca933948f89a5c Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 18 Jan 2022 14:29:27 +0100 Subject: [PATCH 20/36] add: api tests for /encryptionkeys, extend openapi spec --- tests/Fixture/EncryptionKeysFixture.php | 133 ++++++++++++++++++ .../DeleteEncryptionKeyApiTest.php | 54 +++++++ .../IndexEncryptionKeysApiTest.php | 40 ++++++ webroot/docs/openapi.yaml | 128 ++++++++++++++++- 4 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 tests/Fixture/EncryptionKeysFixture.php create mode 100644 tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php create mode 100644 tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php diff --git a/tests/Fixture/EncryptionKeysFixture.php b/tests/Fixture/EncryptionKeysFixture.php new file mode 100644 index 0000000..05b92f8 --- /dev/null +++ b/tests/Fixture/EncryptionKeysFixture.php @@ -0,0 +1,133 @@ +records = [ + [ + 'id' => self::ENCRYPTION_KEY_ORG_A_ID, + 'uuid' => $faker->uuid(), + 'type' => self::TYPE_PGP, + 'encryption_key' => $this->getPublicKey(self::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'owner_model' => 'Organisation', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::ENCRYPTION_KEY_ORG_B_ID, + 'uuid' => $faker->uuid(), + 'type' => self::TYPE_PGP, + 'encryption_key' => $this->getPublicKey(self::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'owner_model' => 'Organisation', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } + + public function getPublicKey(string $type): string + { + switch ($type) { + case self::KEY_TYPE_EDCH: + return <<setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } + + public function testDeleteEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php new file mode 100644 index 0000000..0b7cb98 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php @@ -0,0 +1,40 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index f9418b9..c4852eb 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -23,6 +23,8 @@ tags: description: "Sharing groups are distribution lists usable by tools that can exchange information with a list of trusted partners. Create recurring or ad hoc sharing groups and share them with the members of the sharing group." - name: Broods description: "Cerebrate can connect to other Cerebrate instances to exchange trust information and to instrument interconnectivity between connected local tools. Each such Cerebrate instance with its connected tools is considered to be a brood." + - name: EncryptionKeys + description: "Assign encryption keys to the user, used to securely communicate or validate messages coming from the user." paths: /api/v1/individuals/index: @@ -575,7 +577,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/edit/{sharingGroupId}: + /api/v1/broods/edit/{broodId}: put: summary: "Edit brood" operationId: editBrood @@ -631,6 +633,43 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + # EncryptionKeys + /api/v1/encryptionKeys/index: + get: + summary: "Get encryption keys list" + operationId: getEncryptionKeys + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/encryptionKeys/delete/{encryptionKeyId}: + delete: + summary: "Delete encryption key by ID" + operationId: deleteEncryptionKeyById + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -658,6 +697,18 @@ components: AuthKey: type: string + ModelName: + type: string + enum: + - "Organisation" + - "User" + - "Individual" + - "EncryptionKey" + - "Role" + - "Tag" + - "SharingGroup" + - "Brood" + # Individuals IndividualFirstName: type: string @@ -1089,18 +1140,66 @@ components: type: boolean authkey: $ref: "#/components/schemas/AuthKey" + organisation: + $ref: "#/components/schemas/Organisation" created: $ref: "#/components/schemas/DateTime" modified: $ref: "#/components/schemas/DateTime" - organisation: - $ref: "#/components/schemas/Organisation" BroodList: type: array items: $ref: "#/components/schemas/Brood" + # EncryptionKeys + EncryptionKeyType: + type: string + enum: + - "pgp" + - "smime" + + EncryptionKeyValue: + type: string + example: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + ... + -----END PGP PUBLIC KEY BLOCK----- + + EncryptionKeyExpiration: + type: integer + description: "Timestamp or null of there is no expiration" + nullable: true + + EncryptionKey: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + EncryptionKeyList: + type: array + items: + $ref: "#/components/schemas/EncryptionKey" + # Errors ApiError: type: object @@ -1210,6 +1309,14 @@ components: schema: $ref: "#/components/schemas/ID" + encryptionKeyId: + name: encryptionKeyId + in: path + description: "Numeric ID of the EncryptionKey" + required: true + schema: + $ref: "#/components/schemas/ID" + quickFilter: name: quickFilter in: query @@ -1659,6 +1766,21 @@ components: type: number format: float + # EncryptionKeys + EncryptionKeyResponse: + description: "Encryption key response" + content: + application/json: + schema: + $ref: "#/components/schemas/EncryptionKey" + + EncryptionKeyListResponse: + description: "Encryption key list response" + content: + application/json: + schema: + $ref: "#/components/schemas/EncryptionKeyList" + # Errors ApiErrorResponse: description: "Unexpected API error" From e6daa63064f532062b105612add26537b48d4d61 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 18 Jan 2022 16:11:00 +0100 Subject: [PATCH 21/36] add: more encription keys api endpoints covered --- tests/Fixture/EncryptionKeysFixture.php | 6 +- .../AddEncryptionKeyApiTest.php | 82 ++++++++++++++ .../EditEncryptionKeyApiTest.php | 76 +++++++++++++ .../ViewEncryptionKeyApiTest.php | 40 +++++++ webroot/docs/openapi.yaml | 100 ++++++++++++++++++ 5 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php create mode 100644 tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php create mode 100644 tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php diff --git a/tests/Fixture/EncryptionKeysFixture.php b/tests/Fixture/EncryptionKeysFixture.php index 05b92f8..a9d6c27 100644 --- a/tests/Fixture/EncryptionKeysFixture.php +++ b/tests/Fixture/EncryptionKeysFixture.php @@ -45,7 +45,7 @@ class EncryptionKeysFixture extends TestFixture 'encryption_key' => $this->getPublicKey(self::KEY_TYPE_EDCH), 'revoked' => false, 'expires' => null, - 'owner_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'owner_id' => OrganisationsFixture::ORGANISATION_B_ID, 'owner_model' => 'Organisation', 'created' => $faker->dateTime()->getTimestamp(), 'modified' => $faker->dateTime()->getTimestamp() @@ -54,7 +54,7 @@ class EncryptionKeysFixture extends TestFixture parent::init(); } - public function getPublicKey(string $type): string + public static function getPublicKey(string $type): string { switch ($type) { case self::KEY_TYPE_EDCH: @@ -90,7 +90,7 @@ class EncryptionKeysFixture extends TestFixture } } - private function getPrivateKey(string $type): string + private static function getPrivateKey(string $type): string { switch ($type) { case self::KEY_TYPE_EDCH: diff --git a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php new file mode 100644 index 0000000..b876800 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php @@ -0,0 +1,82 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'type' => EncryptionKeysFixture::TYPE_PGP, + 'encryption_key' => EncryptionKeysFixture::getPublicKey(EncryptionKeysFixture::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => UsersFixture::USER_ADMIN_ID, + 'owner_model' => 'User' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('EncryptionKeys', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddAdminUserEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'type' => EncryptionKeysFixture::TYPE_PGP, + 'encryption_key' => EncryptionKeysFixture::getPublicKey(EncryptionKeysFixture::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => UsersFixture::USER_ADMIN_ID, + 'owner_model' => 'User' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('EncryptionKeys', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php new file mode 100644 index 0000000..6525786 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php @@ -0,0 +1,76 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->put( + $url, + [ + 'revoked' => true, + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'EncryptionKeys', + [ + 'id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID, + 'revoked' => true, + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testRevokeAdminEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID); + $this->put( + $url, + [ + 'revoked' => true + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'EncryptionKeys', + [ + 'id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID, + 'revoked' => true + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php new file mode 100644 index 0000000..8e46119 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php @@ -0,0 +1,40 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index c4852eb..d293ed5 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -652,6 +652,62 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/encryptionKeys/view/{encryptionKeyId}: + get: + summary: "Get encryption key by ID" + operationId: getEncryptionKeyId + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/encryptionKeys/add: + post: + summary: "Add encryption key" + operationId: addEncryptionKey + tags: + - EncryptionKeys + requestBody: + $ref: "#/components/requestBodies/CreateEncryptionKeyRequest" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/encryptionKeys/edit/{encryptionKeyId}: + put: + summary: "Edit encryption key" + operationId: editEncryptionKey + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + requestBody: + $ref: "#/components/requestBodies/EditEncryptionKeyRequest" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/encryptionKeys/delete/{encryptionKeyId}: delete: summary: "Delete encryption key by ID" @@ -1601,6 +1657,50 @@ components: authkey: $ref: "#/components/schemas/AuthKey" + CreateEncryptionKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + + EditEncryptionKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + responses: # Individuals IndividualResponse: From 9551f0b5b82288f4a4513a007926113cd7185d50 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 18 Jan 2022 16:15:45 +0100 Subject: [PATCH 22/36] fix: copy&paste --- tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php index 6525786..56a25d3 100644 --- a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php @@ -10,7 +10,7 @@ use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; use App\Test\Helper\ApiTestTrait; -class EditBroodApiTest extends TestCase +class EditEncryptionKeyApiTest extends TestCase { use IntegrationTestTrait; use ApiTestTrait; From 850eb0fb2d26008c3e01be490834b078b2ab5663 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 18 Jan 2022 17:39:41 +0100 Subject: [PATCH 23/36] add: cover authkeys api endpoints, extend openapi spec --- tests/Fixture/AuthKeysFixture.php | 13 ++ .../Api/AuthKeys/AddAuthKeyApiTest.php | 78 +++++++++ .../Api/AuthKeys/DeleteAuthKeyApiTest.php | 51 ++++++ .../Api/AuthKeys/IndexAuthKeysApiTest.php | 48 ++++++ ...oodsApiTest.php => DeleteBroodApiTest.php} | 4 +- .../DeleteEncryptionKeyApiTest.php | 2 - .../Individuals/DeleteIndividualApiTest.php | 2 - .../DeleteOrganisationApiTest.php | 2 - .../DeleteSharingGroupApiTest.php | 2 - .../TestCase/Api/Users/DeleteUserApiTest.php | 2 - tests/TestCase/Api/Users/EditUserApiTest.php | 2 + webroot/docs/openapi.yaml | 162 +++++++++++++++++- 12 files changed, 348 insertions(+), 20 deletions(-) create mode 100644 tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php create mode 100644 tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php create mode 100644 tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php rename tests/TestCase/Api/Broods/{DeleteBroodsApiTest.php => DeleteBroodApiTest.php} (83%) diff --git a/tests/Fixture/AuthKeysFixture.php b/tests/Fixture/AuthKeysFixture.php index 802ca5c..24034f3 100644 --- a/tests/Fixture/AuthKeysFixture.php +++ b/tests/Fixture/AuthKeysFixture.php @@ -11,9 +11,18 @@ class AuthKeysFixture extends TestFixture { public $connection = 'test'; + public const ADMIN_API_ID = 1; public const ADMIN_API_KEY = 'd033e22ae348aeb5660fc2140aec35850c4da997'; + + + public const SYNC_API_ID = 2; public const SYNC_API_KEY = '6b387ced110858dcbcda36edb044dc18f91a0894'; + + + public const ORG_ADMIN_API_ID = 3; public const ORG_ADMIN_API_KEY = '1c4685d281d478dbcebd494158024bc3539004d0'; + + public const REGULAR_USER_API_ID = 4; public const REGULAR_USER_API_KEY = '12dea96fec20593566ab75692c9949596833adc9'; public function init(): void @@ -23,6 +32,7 @@ class AuthKeysFixture extends TestFixture $this->records = [ [ + 'id' => self::ADMIN_API_ID, 'uuid' => $faker->uuid(), 'authkey' => $hasher->hash(self::ADMIN_API_KEY), 'authkey_start' => substr(self::ADMIN_API_KEY, 0, 4), @@ -34,6 +44,7 @@ class AuthKeysFixture extends TestFixture 'modified' => $faker->dateTime()->getTimestamp() ], [ + 'id' => self::SYNC_API_ID, 'uuid' => $faker->uuid(), 'authkey' => $hasher->hash(self::SYNC_API_KEY), 'authkey_start' => substr(self::SYNC_API_KEY, 0, 4), @@ -45,6 +56,7 @@ class AuthKeysFixture extends TestFixture 'modified' => $faker->dateTime()->getTimestamp() ], [ + 'id' => self::ORG_ADMIN_API_ID, 'uuid' => $faker->uuid(), 'authkey' => $hasher->hash(self::ORG_ADMIN_API_KEY), 'authkey_start' => substr(self::ORG_ADMIN_API_KEY, 0, 4), @@ -56,6 +68,7 @@ class AuthKeysFixture extends TestFixture 'modified' => $faker->dateTime()->getTimestamp() ], [ + 'id' => self::REGULAR_USER_API_ID, 'uuid' => $faker->uuid(), 'authkey' => $hasher->hash(self::REGULAR_USER_API_KEY), 'authkey_start' => substr(self::REGULAR_USER_API_KEY, 0, 4), diff --git a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php new file mode 100644 index 0000000..810dad2 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php @@ -0,0 +1,78 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'authkey' => $faker->sha1, + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'comment' => $faker->text + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('AuthKeys', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddAdminAuthKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'authkey' => $faker->sha1, + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'comment' => $faker->text + ] + ); + + $this->assertResponseCode(404); + $this->addWarning('Should return 405 Method Not Allowed instead of 404 Not Found'); + $this->assertDbRecordNotExists('AuthKeys', ['uuid' => $uuid]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php new file mode 100644 index 0000000..21a22f6 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php @@ -0,0 +1,51 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, AuthKeysFixture::ADMIN_API_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('AuthKeys', ['id' => AuthKeysFixture::ADMIN_API_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + } + + public function testDeleteOrgAdminAuthKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, AuthKeysFixture::ORG_ADMIN_API_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('AuthKeys', ['id' => AuthKeysFixture::ORG_ADMIN_API_ID]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'delete'); + } +} diff --git a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php new file mode 100644 index 0000000..9fbe3e6 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php @@ -0,0 +1,48 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', AuthKeysFixture::ADMIN_API_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } + + public function testIndexDoesNotShowAdminAuthKeysAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseNotContains(sprintf('"id": %d', AuthKeysFixture::REGULAR_USER_API_KEY)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php similarity index 83% rename from tests/TestCase/Api/Broods/DeleteBroodsApiTest.php rename to tests/TestCase/Api/Broods/DeleteBroodApiTest.php index 94aa0a8..1b90bd6 100644 --- a/tests/TestCase/Api/Broods/DeleteBroodsApiTest.php +++ b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php @@ -10,7 +10,7 @@ use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\BroodsFixture; use App\Test\Helper\ApiTestTrait; -class DeleteBroodsApiTest extends TestCase +class DeleteBroodApiTest extends TestCase { use IntegrationTestTrait; use ApiTestTrait; @@ -36,7 +36,6 @@ class DeleteBroodsApiTest extends TestCase $this->assertDbRecordNotExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteBroodNotAllowedAsRegularUser(): void @@ -49,6 +48,5 @@ class DeleteBroodsApiTest extends TestCase $this->assertDbRecordExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php index 934d3e7..02ecd25 100644 --- a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php @@ -36,7 +36,6 @@ class DeleteEncryptionKeyApiTest extends TestCase $this->assertDbRecordNotExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteEncryptionKeyNotAllowedAsRegularUser(): void @@ -49,6 +48,5 @@ class DeleteEncryptionKeyApiTest extends TestCase $this->assertDbRecordExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php index e50cf63..32b4ba7 100644 --- a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -35,7 +35,6 @@ class DeleteIndividualApiTest extends TestCase $this->assertDbRecordNotExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteIndividualNotAllowedAsRegularUser(): void @@ -48,6 +47,5 @@ class DeleteIndividualApiTest extends TestCase $this->assertDbRecordExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index 6a323fb..12b62d1 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -35,7 +35,6 @@ class DeleteOrganisationApiTest extends TestCase $this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteOrganisationNotAllowedAsRegularUser(): void @@ -48,6 +47,5 @@ class DeleteOrganisationApiTest extends TestCase $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php index c93bb05..36379d4 100644 --- a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -36,7 +36,6 @@ class DeleteSharingGroupApiTest extends TestCase $this->assertDbRecordNotExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteSharingGroupNotAllowedAsRegularUser(): void @@ -49,6 +48,5 @@ class DeleteSharingGroupApiTest extends TestCase $this->assertDbRecordExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 50df2e5..48f9069 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -37,7 +37,6 @@ class DeleteUserApiTest extends TestCase $this->assertDbRecordNotExists('Users', ['id' => UsersFixture::USER_REGULAR_USER_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } public function testDeleteUserNotAllowedAsRegularUser(): void @@ -50,6 +49,5 @@ class DeleteUserApiTest extends TestCase $this->assertDbRecordExists('Users', ['id' => UsersFixture::USER_ORG_ADMIN_ID]); //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec($url, 'delete'); - $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } } diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 8d2810d..39673ee 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -57,6 +57,7 @@ class EditUserApiTest extends TestCase ] ); + $this->assertResponseOk(); $this->assertDbRecordNotExists('Users', [ 'id' => UsersFixture::USER_REGULAR_USER_ID, 'role_id' => RolesFixture::ROLE_ADMIN_ID @@ -75,6 +76,7 @@ class EditUserApiTest extends TestCase ] ); + $this->assertResponseOk(); $this->assertDbRecordExists('Users', [ 'id' => UsersFixture::USER_REGULAR_USER_ID, 'username' => 'test' diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index d293ed5..076af9f 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -25,6 +25,8 @@ tags: description: "Cerebrate can connect to other Cerebrate instances to exchange trust information and to instrument interconnectivity between connected local tools. Each such Cerebrate instance with its connected tools is considered to be a brood." - name: EncryptionKeys description: "Assign encryption keys to the user, used to securely communicate or validate messages coming from the user." + - name: AuthKeys + description: "Authkeys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries Cerebrate, add additional keys. Use the comment field to make identifying your keys easier." paths: /api/v1/individuals/index: @@ -726,6 +728,61 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + # AuthKeys + /api/v1/authKeys/index: + get: + summary: "Get auth keys list" + operationId: getAuthKeys + tags: + - AuthKeys + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/AuthKeyListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/authKeys/add: + post: + summary: "Add auth keys" + operationId: addAuthKey + tags: + - AuthKeys + requestBody: + $ref: "#/components/requestBodies/CreateAuthKeyRequest" + responses: + "200": + $ref: "#/components/responses/AuthKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/authKeys/delete/{authKeyId}: + delete: + summary: "Delete auth key by ID" + operationId: deleteAuthKeyById + tags: + - AuthKeys + parameters: + - $ref: "#/components/parameters/authKeyId" + responses: + "200": + $ref: "#/components/responses/AuthKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -750,9 +807,6 @@ components: format: email example: "user@example.com" - AuthKey: - type: string - ModelName: type: string enum: @@ -1195,7 +1249,7 @@ components: skip_proxy: type: boolean authkey: - $ref: "#/components/schemas/AuthKey" + $ref: "#/components/schemas/AuthKeyRaw" organisation: $ref: "#/components/schemas/Organisation" created: @@ -1224,7 +1278,7 @@ components: EncryptionKeyExpiration: type: integer - description: "Timestamp or null of there is no expiration" + description: "UNIX timestamp or null of there is no expiration" nullable: true EncryptionKey: @@ -1256,6 +1310,58 @@ components: items: $ref: "#/components/schemas/EncryptionKey" + # AuthKeys + AuthKeyRaw: + type: string + + AuthKeyHashed: + type: string + + AuthKeyExpiration: + type: integer + description: "0 or UNIX timestamp" + example: 0 + + AuthKeyCreatedAt: + type: integer + description: "UNIX timestamp" + + AuthKeyComment: + type: string + + AuthKey: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + authkey: + $ref: "#/components/schemas/AuthKeyHashed" + authkey_start: + type: string + example: abcd + authkey_end: + type: string + example: abcd + created: + $ref: "#/components/schemas/AuthKeyCreatedAt" + expiration: + $ref: "#/components/schemas/AuthKeyExpiration" + type: integer + description: "0 or UNIX timestamp" + user_id: + $ref: "#/components/schemas/ID" + comment: + $ref: "#/components/schemas/AuthKeyComment" + user: + $ref: "#/components/schemas/User" + + AuthKeyList: + type: array + items: + $ref: "#/components/schemas/AuthKey" + # Errors ApiError: type: object @@ -1373,6 +1479,14 @@ components: schema: $ref: "#/components/schemas/ID" + authKeyId: + name: authKeyId + in: path + description: "Numeric ID of the AuthKey" + required: true + schema: + $ref: "#/components/schemas/ID" + quickFilter: name: quickFilter in: query @@ -1629,7 +1743,7 @@ components: skip_proxy: type: boolean authkey: - $ref: "#/components/schemas/AuthKey" + $ref: "#/components/schemas/AuthKeyRaw" EditBroodRequest: required: true @@ -1655,7 +1769,7 @@ components: skip_proxy: type: boolean authkey: - $ref: "#/components/schemas/AuthKey" + $ref: "#/components/schemas/AuthKeyRaw" CreateEncryptionKeyRequest: required: true @@ -1701,6 +1815,25 @@ components: owner_model: $ref: "#/components/schemas/ModelName" + # AuthKeys + CreateAuthKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + authkey: + $ref: "#/components/schemas/AuthKeyRaw" + expiration: + $ref: "#/components/schemas/AuthKeyExpiration" + user_id: + $ref: "#/components/schemas/ID" + comment: + $ref: "#/components/schemas/AuthKeyComment" + responses: # Individuals IndividualResponse: @@ -1881,6 +2014,21 @@ components: schema: $ref: "#/components/schemas/EncryptionKeyList" + # AuthKeys + AuthKeyResponse: + description: "Auth key response" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthKey" + + AuthKeyListResponse: + description: "Auth key list response" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthKeyList" + # Errors ApiErrorResponse: description: "Unexpected API error" From 20cc6017d0472c33ffddfe496db9cf93f2e40fad Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 19 Jan 2022 09:04:10 +0100 Subject: [PATCH 24/36] fix: [localTool:CommonConnector] Ensure one logger per connector --- .../CommonConnectorTools.php | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php index 213db17..d01e9d9 100644 --- a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -24,21 +24,24 @@ class CommonConnectorTools public function __construct() { - Log::setConfig("LocalToolDebug", [ - 'className' => FileLog::class, - 'path' => LOGS, - 'file' => "{$this->connectorName}-debug", - 'scopes' => [$this->connectorName], - 'levels' => ['notice', 'info', 'debug'], - ]); - Log::setConfig("LocalToolError", [ - 'className' => FileLog::class, - 'path' => LOGS, - 'file' => "{$this->connectorName}-error", - 'scopes' => [$this->connectorName], - 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], - ]); - + if (empty(Log::getConfig("LocalToolDebug{$this->connectorName}"))) { + Log::setConfig("LocalToolDebug{$this->connectorName}", [ + 'className' => FileLog::class, + 'path' => LOGS, + 'file' => "{$this->connectorName}-debug", + 'scopes' => [$this->connectorName], + 'levels' => ['notice', 'info', 'debug'], + ]); + } + if (empty(Log::getConfig("LocalToolError{$this->connectorName}"))) { + Log::setConfig("LocalToolError{$this->connectorName}", [ + 'className' => FileLog::class, + 'path' => LOGS, + 'file' => "{$this->connectorName}-error", + 'scopes' => [$this->connectorName], + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + ]); + } } protected function logDebug($message) From 1d7fc00a650d08cc0a0f77dd4f8458edafd4736f Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 19 Jan 2022 09:33:57 +0100 Subject: [PATCH 25/36] chg: [layout:header-profile] Improved spacing --- templates/element/layouts/header/header-profile.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/element/layouts/header/header-profile.php b/templates/element/layouts/header/header-profile.php index 1f148d6..146aa3e 100644 --- a/templates/element/layouts/header/header-profile.php +++ b/templates/element/layouts/header/header-profile.php @@ -11,7 +11,10 @@ use Cake\Routing\Router;
SocialProvider->getIcon($this->request->getAttribute('identity')) ?> - [] request->getAttribute('identity')['username']) ?> + + [] + request->getAttribute('identity')['username']) ?> +
From 5eca1a916052c23f304eb6410a1d2cdfcc6a624d Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 10:45:51 +0100 Subject: [PATCH 26/36] add: change password via api test, add helper methods to ApiTestTrait. --- tests/Helper/ApiTestTrait.php | 30 +++++++ .../Api/Users/ChangePasswordApiTest.php | 78 +++++++++++++++++++ tests/TestCase/Api/Users/EditUserApiTest.php | 20 +---- 3 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 tests/TestCase/Api/Users/ChangePasswordApiTest.php diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 8e20a36..56728f4 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -129,4 +129,34 @@ trait ApiTestTrait } $this->assertEmpty($record); } + + /** + * Parses the response body and returns the decoded JSON + * + * @return void + * @throws \Exception + * + * @see https://book.cakephp.org/4/en/orm-query-builder.html + */ + public function getJsonResponseAsArray(): array + { + if ($this->_response->getHeaders()['Content-Type'][0] !== 'application/json') { + throw new \Exception('The response is not a JSON response'); + } + + return json_decode((string)$this->_response->getBody(), true); + } + + /** + * Gets a database records as an array + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return array + * @throws \Cake\Datasource\Exception\RecordNotFoundException + */ + public function getRecordFromDb(string $table, array $conditions): array + { + return $this->getTableLocator()->get($table)->find()->where($conditions)->first()->toArray(); + } } diff --git a/tests/TestCase/Api/Users/ChangePasswordApiTest.php b/tests/TestCase/Api/Users/ChangePasswordApiTest.php new file mode 100644 index 0000000..f1c1b82 --- /dev/null +++ b/tests/TestCase/Api/Users/ChangePasswordApiTest.php @@ -0,0 +1,78 @@ +initializeOpenApiValidator($_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'); + + $this->collection = new ComponentRegistry(); + $this->auth = new FormAuthenticate($this->collection, [ + 'userModel' => 'Users', + ]); + } + + public function testChangePasswordOwnUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $newPassword = 'Test12345678!'; + + $this->put( + self::ENDPOINT, + [ + 'password' => $newPassword, + ] + ); + + $this->assertResponseOk(); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); + + // Test new password with form login + $request = new ServerRequest([ + 'url' => 'users/login', + 'post' => [ + 'username' => UsersFixture::USER_REGULAR_USER_USERNAME, + 'password' => $newPassword + ], + ]); + $result = $this->auth->authenticate($request, new Response()); + + $this->assertEquals(UsersFixture::USER_REGULAR_USER_ID, $result['id']); + $this->assertEquals(UsersFixture::USER_REGULAR_USER_USERNAME, $result['username']); + } +} diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 39673ee..d52aea9 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -10,6 +10,7 @@ use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; use App\Test\Fixture\RolesFixture; use App\Test\Helper\ApiTestTrait; +use Authentication\PasswordHasher\DefaultPasswordHasher; class EditUserApiTest extends TestCase { @@ -65,23 +66,4 @@ class EditUserApiTest extends TestCase //TODO: $this->assertRequestMatchesOpenApiSpec(); $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); } - - public function testEditSelfUser(): void - { - $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); - $this->put( - self::ENDPOINT, - [ - 'username' => 'test', - ] - ); - - $this->assertResponseOk(); - $this->assertDbRecordExists('Users', [ - 'id' => UsersFixture::USER_REGULAR_USER_ID, - 'username' => 'test' - ]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); - } } From 5a06a97c321437e6e99e7d94c1e58cc866ece319 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 19 Jan 2022 14:04:13 +0100 Subject: [PATCH 27/36] new: [doc] Added prerequisites document --- documentation/prerequisites.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 documentation/prerequisites.md diff --git a/documentation/prerequisites.md b/documentation/prerequisites.md new file mode 100644 index 0000000..98da39d --- /dev/null +++ b/documentation/prerequisites.md @@ -0,0 +1,17 @@ +# Prerequisites based on usecases + +This document list the requirements that have to be met in order to perform the desired usecase. + +## Connect a local tool to cerebrate +- **Networking**: The *cerebrate* application must be able to contact the local tool service. That means the address and the port of the local must be reachable by *cerebrate*. +- **User permissions**: Depends on the actions performed by Cerebrate on the local tool. + - Example: For a standard MISP configuration, a simple user with the `user` role is enough for Cerebrate to pass the health check. + +## Conect two cerebrate instances together +- **Networking**: The two *cerebrate* applications must be able to contact each others. That means the address and the port of both tools must be reachable by the other one. +- **User permissions**: No specific role or set of permission is required. Any user role can be used. + +## Connect two local tools through cerebrate +- **Networking**: The two *cerebrate* applications must be able to contact each others. That means the address and the port of both tools must be reachable by the other one. This also applies to both the local tools. +- **User permissions**: Depends on the actions performed by Cerebrate on the local tool. + - Example: For a standard MISP configuration, in order to have two instance connected, the API key used by *cebrate* to orchestrate the inter-connection must belong to a user having the `site-admin` permission flag. This is essential as only the `site-admin` permission allows to create synchronisation links between MISP instances. \ No newline at end of file From c7ccff5e1e22e279a460b0ef463d432da7080ab8 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 19 Jan 2022 14:19:55 +0100 Subject: [PATCH 28/36] fix: [doc] Typo in text --- documentation/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/prerequisites.md b/documentation/prerequisites.md index 98da39d..7b020ad 100644 --- a/documentation/prerequisites.md +++ b/documentation/prerequisites.md @@ -3,7 +3,7 @@ This document list the requirements that have to be met in order to perform the desired usecase. ## Connect a local tool to cerebrate -- **Networking**: The *cerebrate* application must be able to contact the local tool service. That means the address and the port of the local must be reachable by *cerebrate*. +- **Networking**: The *cerebrate* application must be able to contact the local tool service. That means the address and the port of the local tool must be reachable by *cerebrate*. - **User permissions**: Depends on the actions performed by Cerebrate on the local tool. - Example: For a standard MISP configuration, a simple user with the `user` role is enough for Cerebrate to pass the health check. From 7ed87ff0f2c660ea18604f394bac9f6900b8490c Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 15:15:49 +0100 Subject: [PATCH 29/36] chg: refactor ApiTestTrait to reduce code duplication, enforce openapi spec validations --- tests/Helper/ApiTestTrait.php | 164 ++++++++++++++++-- tests/README.md | 6 + .../Api/AuthKeys/AddAuthKeyApiTest.php | 6 - .../Api/AuthKeys/DeleteAuthKeyApiTest.php | 8 +- .../Api/AuthKeys/IndexAuthKeysApiTest.php | 6 - tests/TestCase/Api/Broods/AddBroodApiTest.php | 6 - .../Api/Broods/DeleteBroodApiTest.php | 6 - .../TestCase/Api/Broods/EditBroodApiTest.php | 6 - .../Api/Broods/IndexBroodsApiTest.php | 4 - .../Api/Broods/TestBroodConnectionApiTest.php | 4 - .../TestCase/Api/Broods/ViewBroodApiTest.php | 4 - .../AddEncryptionKeyApiTest.php | 6 - .../DeleteEncryptionKeyApiTest.php | 4 - .../EditEncryptionKeyApiTest.php | 6 - .../IndexEncryptionKeysApiTest.php | 4 - .../ViewEncryptionKeyApiTest.php | 4 - .../Api/Inbox/CreateInboxEntryApiTest.php | 6 - .../TestCase/Api/Inbox/IndexInboxApiTest.php | 4 - .../Api/Individuals/AddIndividualApiTest.php | 36 ++-- .../Individuals/DeleteIndividualApiTest.php | 6 - .../Api/Individuals/EditIndividualApiTest.php | 6 - .../Individuals/IndexIndividualsApiTest.php | 4 - .../Api/Individuals/ViewIndividualApiTest.php | 4 - .../Organisations/AddOrganisationApiTest.php | 6 - .../DeleteOrganisationApiTest.php | 5 - .../Organisations/EditOrganisationApiTest.php | 6 - .../IndexOrganisationsApiTest.php | 4 - .../Organisations/TagOrganisationApiTest.php | 6 - .../UntagOrganisationApiTest.php | 6 - .../Organisations/ViewOrganisationApiTest.php | 5 - .../SharingGroups/AddSharingGroupApiTest.php | 6 - .../DeleteSharingGroupApiTest.php | 6 - .../SharingGroups/EditSharingGroupApiTest.php | 7 - .../IndexSharingGroupsApiTest.php | 4 - .../SharingGroups/ViewSharingGroupApiTest.php | 4 - tests/TestCase/Api/Tags/IndexTagsApiTest.php | 5 - tests/TestCase/Api/Users/AddUserApiTest.php | 6 - .../Api/Users/ChangePasswordApiTest.php | 4 - .../TestCase/Api/Users/DeleteUserApiTest.php | 8 - tests/TestCase/Api/Users/EditUserApiTest.php | 7 - .../TestCase/Api/Users/IndexUsersApiTest.php | 4 - tests/TestCase/Api/Users/ViewUserApiTest.php | 6 - webroot/docs/openapi.yaml | 6 +- 43 files changed, 172 insertions(+), 249 deletions(-) diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 56728f4..2060063 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -4,25 +4,45 @@ declare(strict_types=1); namespace App\Test\Helper; +use Cake\TestSuite\IntegrationTestTrait; use Cake\Http\Exception\NotImplementedException; +use Cake\Http\ServerRequestFactory; +use Cake\Http\ServerRequest; use \League\OpenAPIValidation\PSR7\ValidatorBuilder; use \League\OpenAPIValidation\PSR7\RequestValidator; use \League\OpenAPIValidation\PSR7\ResponseValidator; use \League\OpenAPIValidation\PSR7\OperationAddress; +use PHPUnit\Exception as PHPUnitException; +/** + * Trait ApiTestTrait + * + * @package App\Test\TestCase\Helper + */ trait ApiTestTrait { + use IntegrationTestTrait { + IntegrationTestTrait::_buildRequest as _buildRequestOriginal; + IntegrationTestTrait::_sendRequest as _sendRequestOriginal; + } + /** @var string */ protected $_authToken = ''; /** @var ValidatorBuilder */ - private $validator; + private $_validator; /** @var RequestValidator */ - private $requestValidator; + private $_requestValidator; /** @var ResponseValidator */ - private $responseValidator; + private $_responseValidator; + + /** @var ServerRequest */ + protected $_psrRequest; + + /* @var boolean */ + protected $_skipOpenApiValidations = false; public function setUp(): void { @@ -40,11 +60,22 @@ trait ApiTestTrait $this->configRequest([ 'headers' => [ 'Accept' => 'application/json', - 'Authorization' => $this->_authToken + 'Authorization' => $this->_authToken, + 'Content-Type' => 'application/json' ] ]); } + /** + * Skip OpenAPI validations. + * + * @return void + */ + public function skipOpenApiValidations(): void + { + $this->_skipOpenApiValidations = true; + } + public function assertResponseContainsArray(array $expected): void { $responseArray = json_decode((string)$this->_response->getBody(), true); @@ -59,22 +90,19 @@ trait ApiTestTrait */ public function initializeOpenApiValidator(string $specFile): void { - $this->validator = (new ValidatorBuilder)->fromYamlFile($specFile); - $this->requestValidator = $this->validator->getRequestValidator(); - $this->responseValidator = $this->validator->getResponseValidator(); + $this->_validator = (new ValidatorBuilder)->fromYamlFile($specFile); + $this->_requestValidator = $this->_validator->getRequestValidator(); + $this->_responseValidator = $this->_validator->getResponseValidator(); } /** * Validates the API request against the OpenAPI spec * - * @param string $path The path to the API endpoint - * @param string $method The HTTP method used to call the endpoint * @return void */ - public function assertRequestMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void + public function assertRequestMatchesOpenApiSpec(): void { - // TODO: find a workaround to create a PSR-7 request object for validation - throw NotImplementedException("Unfortunately cakephp does not save the PSR-7 request object in the test context"); + $this->_requestValidator->validate($this->_psrRequest); } /** @@ -87,7 +115,7 @@ trait ApiTestTrait public function assertResponseMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void { $address = new OperationAddress($endpoint, $method); - $this->responseValidator->validate($address, $this->_response); + $this->_responseValidator->validate($address, $this->_response); } /** @@ -111,7 +139,7 @@ trait ApiTestTrait } /** - * Validates a record do notexists in the database + * Validates a record do not exists in the database * * @param string $table The table name * @param array $conditions The conditions to check @@ -133,10 +161,8 @@ trait ApiTestTrait /** * Parses the response body and returns the decoded JSON * - * @return void + * @return array * @throws \Exception - * - * @see https://book.cakephp.org/4/en/orm-query-builder.html */ public function getJsonResponseAsArray(): array { @@ -159,4 +185,108 @@ trait ApiTestTrait { return $this->getTableLocator()->get($table)->find()->where($conditions)->first()->toArray(); } + + /** + * This method intercepts IntegrationTestTrait::_buildRequest() + * in the quest to get a PSR-7 request object and saves it for + * later inspection, also validates it against the OpenAPI spec. + * @see \Cake\TestSuite\IntegrationTestTrait::_buildRequest() + * + * @param string $url The URL + * @param string $method The HTTP method + * @param array|string $data The request data. + * @return array The request context + */ + protected function _buildRequest(string $url, $method, $data = []): array + { + $spec = $this->_buildRequestOriginal($url, $method, $data); + + $this->_psrRequest = $this->_createPsr7RequestFromSpec($spec); + + // Validate request against OpenAPI spec + if (!$this->_skipOpenApiValidations) { + try { + $this->assertRequestMatchesOpenApiSpec(); + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } else { + $this->addWarning( + sprintf( + 'OpenAPI spec validations skipped for request [%s]%s.', + $this->_psrRequest->getMethod(), + $this->_psrRequest->getPath() + ) + ); + } + + return $spec; + } + + /** + * This method intercepts IntegrationTestTrait::_buildRequest() + * and validates the response against the OpenAPI spec. + * + * @see \Cake\TestSuite\IntegrationTestTrait::_sendRequest() + * + * @param array|string $url The URL + * @param string $method The HTTP method + * @param array|string $data The request data. + * @return void + * @throws \PHPUnit\Exception|\Throwable + */ + protected function _sendRequest($url, $method, $data = []): void + { + // Adding Content-Type: application/json $this->configRequest() prevents this from happening somehow + if (in_array($method, ['POST', 'PATCH', 'PUT']) && $this->_request['headers']['Content-Type'] === 'application/json') { + $data = json_encode($data); + } + + $this->_sendRequestOriginal($url, $method, $data); + + // Validate response against OpenAPI spec + if (!$this->_skipOpenApiValidations) { + $this->assertResponseMatchesOpenApiSpec( + $this->_psrRequest->getPath(), + strtolower($this->_psrRequest->getMethod()) + ); + } else { + $this->addWarning( + sprintf( + 'OpenAPI spec validations skipped for response of [%s]%s.', + $this->_psrRequest->getMethod(), + $this->_psrRequest->getPath() + ) + ); + } + } + + /** + * Create a PSR-7 request from the request spec. + * @see \Cake\TestSuite\MiddlewareDispatcher::_createRequest() + * + * @param array $spec The request spec. + * @return \Cake\Http\ServerRequest + */ + private function _createPsr7RequestFromSpec(array $spec): ServerRequest + { + if (isset($spec['input'])) { + $spec['post'] = []; + $spec['environment']['CAKEPHP_INPUT'] = $spec['input']; + } + $environment = array_merge( + array_merge($_SERVER, ['REQUEST_URI' => $spec['url']]), + $spec['environment'] + ); + if (strpos($environment['PHP_SELF'], 'phpunit') !== false) { + $environment['PHP_SELF'] = '/'; + } + return ServerRequestFactory::fromGlobals( + $environment, + $spec['query'], + $spec['post'], + $spec['cookies'], + $spec['files'] + ); + } } diff --git a/tests/README.md b/tests/README.md index e080442..d5f4385 100644 --- a/tests/README.md +++ b/tests/README.md @@ -115,6 +115,12 @@ The default OpenAPI spec path is set in `phpunit.xml` as a environment variablea ``` + +### Debugging tests +``` +$ export XDEBUG_CONFIG="idekey=IDEKEY" +$ phpunit +``` ## TODO - [ ] Validate API requests against the OpenAPI spec diff --git a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php index 810dad2..32a79c4 100644 --- a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php +++ b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class AddAuthKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/authKeys/add'; @@ -46,8 +44,6 @@ class AddAuthKeyApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); $this->assertDbRecordExists('AuthKeys', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddAdminAuthKeyNotAllowedAsRegularUser(): void @@ -72,7 +68,5 @@ class AddAuthKeyApiTest extends TestCase $this->assertResponseCode(404); $this->addWarning('Should return 405 Method Not Allowed instead of 404 Not Found'); $this->assertDbRecordNotExists('AuthKeys', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php index 21a22f6..539ab29 100644 --- a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php +++ b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php @@ -4,15 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; -use App\Test\Fixture\BroodsFixture; use App\Test\Helper\ApiTestTrait; class DeleteAuthKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/authKeys/delete'; @@ -33,19 +30,16 @@ class DeleteAuthKeyApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('AuthKeys', ['id' => AuthKeysFixture::ADMIN_API_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteOrgAdminAuthKeyNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, AuthKeysFixture::ORG_ADMIN_API_ID); + $this->delete($url); $this->assertResponseCode(405); $this->assertDbRecordExists('AuthKeys', ['id' => AuthKeysFixture::ORG_ADMIN_API_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php index 9fbe3e6..43a3390 100644 --- a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php +++ b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Helper\ApiTestTrait; class IndexAuthKeysApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/authKeys/index'; @@ -31,8 +29,6 @@ class IndexAuthKeysApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', AuthKeysFixture::ADMIN_API_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } public function testIndexDoesNotShowAdminAuthKeysAsRegularUser(): void @@ -42,7 +38,5 @@ class IndexAuthKeysApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseNotContains(sprintf('"id": %d', AuthKeysFixture::REGULAR_USER_API_KEY)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Broods/AddBroodApiTest.php b/tests/TestCase/Api/Broods/AddBroodApiTest.php index 14fb2c5..3da884c 100644 --- a/tests/TestCase/Api/Broods/AddBroodApiTest.php +++ b/tests/TestCase/Api/Broods/AddBroodApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\AuthKeysFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class AddBroodApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/broods/add'; @@ -51,8 +49,6 @@ class AddBroodApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); $this->assertDbRecordExists('Broods', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddBroodNotAllowedAsRegularUser(): void @@ -79,7 +75,5 @@ class AddBroodApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('Broods', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/Broods/DeleteBroodApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php index 1b90bd6..c54e47a 100644 --- a/tests/TestCase/Api/Broods/DeleteBroodApiTest.php +++ b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\BroodsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class DeleteBroodApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/broods/delete'; @@ -34,8 +32,6 @@ class DeleteBroodApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteBroodNotAllowedAsRegularUser(): void @@ -46,7 +42,5 @@ class DeleteBroodApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/Broods/EditBroodApiTest.php b/tests/TestCase/Api/Broods/EditBroodApiTest.php index d8fa01f..9bf3077 100644 --- a/tests/TestCase/Api/Broods/EditBroodApiTest.php +++ b/tests/TestCase/Api/Broods/EditBroodApiTest.php @@ -7,13 +7,11 @@ namespace App\Test\TestCase\Api\Users; use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; -use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\BroodsFixture; use App\Test\Helper\ApiTestTrait; class EditBroodApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/broods/edit'; @@ -47,8 +45,6 @@ class EditBroodApiTest extends TestCase 'name' => 'Test Brood 4321', ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testEditBroodNotAllowedAsRegularUser(): void @@ -71,7 +67,5 @@ class EditBroodApiTest extends TestCase 'name' => 'Test Brood 1234' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } } diff --git a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php index 856b151..598898d 100644 --- a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php +++ b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\BroodsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexBroodsApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/index'; @@ -33,7 +31,5 @@ class IndexBroodsApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php index 1084131..31c2d48 100644 --- a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php +++ b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\BroodsFixture; @@ -14,7 +13,6 @@ use \WireMock\Client\WireMock; class TestBroodConnectionApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; use WireMockTestTrait; @@ -46,8 +44,6 @@ class TestBroodConnectionApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains('"user": "wiremock"'); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } private function mockCerebrateStatusResponse(): \WireMock\Stubbing\StubMapping diff --git a/tests/TestCase/Api/Broods/ViewBroodApiTest.php b/tests/TestCase/Api/Broods/ViewBroodApiTest.php index 35bb957..c357d90 100644 --- a/tests/TestCase/Api/Broods/ViewBroodApiTest.php +++ b/tests/TestCase/Api/Broods/ViewBroodApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\BroodsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class ViewBroodApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/broods/view'; @@ -34,7 +32,5 @@ class ViewBroodApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php index b876800..1adf5d9 100644 --- a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; @@ -13,7 +12,6 @@ use App\Test\Helper\ApiTestTrait; class AddEncryptionKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/encryptionKeys/add'; @@ -50,8 +48,6 @@ class AddEncryptionKeyApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); $this->assertDbRecordExists('EncryptionKeys', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddAdminUserEncryptionKeyNotAllowedAsRegularUser(): void @@ -76,7 +72,5 @@ class AddEncryptionKeyApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('EncryptionKeys', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php index 02ecd25..4bf0174 100644 --- a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class DeleteEncryptionKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/encryptionKeys/delete'; @@ -46,7 +44,5 @@ class DeleteEncryptionKeyApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php index 56a25d3..300a969 100644 --- a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class EditEncryptionKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/encryptionKeys/edit'; @@ -46,8 +44,6 @@ class EditEncryptionKeyApiTest extends TestCase 'revoked' => true, ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testRevokeAdminEncryptionKeyNotAllowedAsRegularUser(): void @@ -70,7 +66,5 @@ class EditEncryptionKeyApiTest extends TestCase 'revoked' => true ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } } diff --git a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php index 0b7cb98..9aec4c5 100644 --- a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexEncryptionKeysApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/encryptionKeys/index'; @@ -34,7 +32,5 @@ class IndexEncryptionKeysApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php index 8e46119..39c6519 100644 --- a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\EncryptionKeysFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class ViewEncryptionKeyApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/encryptionKeys/view'; @@ -34,7 +32,5 @@ class ViewEncryptionKeyApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php index 39879f3..9c34aa0 100644 --- a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Helper\ApiTestTrait; class CreateInboxEntryApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/inbox/createEntry'; @@ -51,8 +49,6 @@ class CreateInboxEntryApiTest extends TestCase 'action' => 'Registration', ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } public function testAddUserRegistrationInboxNotAllowedAsRegularUser(): void @@ -70,7 +66,5 @@ class CreateInboxEntryApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('Inbox', ['id' => 3]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } } diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php index 46f1b92..da99710 100644 --- a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\InboxFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexInboxApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/inbox/index'; @@ -34,7 +32,5 @@ class IndexInboxApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_USER_REGISTRATION_ID)); $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_INCOMING_CONNECTION_REQUEST_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php index 0320c30..cced4e4 100644 --- a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Helper\ApiTestTrait; class AddIndividualApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/individuals/add'; @@ -40,26 +38,22 @@ class AddIndividualApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains('"email": "john@example.com"'); $this->assertDbRecordExists('Individuals', ['email' => 'john@example.com']); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } - public function testAddUserNotAllowedAsRegularUser(): void - { - $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); - $this->post( - self::ENDPOINT, - [ - 'email' => 'john@example.com', - 'first_name' => 'John', - 'last_name' => 'Doe', - 'position' => 'Security Analyst' - ] - ); + // public function testAddUserNotAllowedAsRegularUser(): void + // { + // $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + // $this->post( + // self::ENDPOINT, + // [ + // 'email' => 'john@example.com', + // 'first_name' => 'John', + // 'last_name' => 'Doe', + // 'position' => 'Security Analyst' + // ] + // ); - $this->assertResponseCode(405); - $this->assertDbRecordNotExists('Individuals', ['email' => 'john@example.com']); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); - } + // $this->assertResponseCode(405); + // $this->assertDbRecordNotExists('Individuals', ['email' => 'john@example.com']); + // } } diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php index 32b4ba7..0768b59 100644 --- a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\IndividualsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class DeleteIndividualApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/individuals/delete'; @@ -33,8 +31,6 @@ class DeleteIndividualApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteIndividualNotAllowedAsRegularUser(): void @@ -45,7 +41,5 @@ class DeleteIndividualApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php index 642482c..cdff2d9 100644 --- a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\IndividualsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class EditIndividualApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/individuals/edit'; @@ -41,8 +39,6 @@ class EditIndividualApiTest extends TestCase 'id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, 'email' => 'foo@bar.com' ]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testEditAnyIndividualNotAllowedAsRegularUser(): void @@ -61,7 +57,5 @@ class EditIndividualApiTest extends TestCase 'id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, 'email' => 'foo@bar.com' ]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } } diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php index d8254d1..29da85f 100644 --- a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\IndividualsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexIndividualsApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/individuals/index'; @@ -32,7 +30,5 @@ class IndexIndividualsApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php index 5d47610..8589f1f 100644 --- a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\IndividualsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class ViewIndividualApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/individuals/view'; @@ -33,7 +31,5 @@ class ViewIndividualApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php index d561292..bde14a0 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Helper\ApiTestTrait; class AddOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/add'; @@ -47,8 +45,6 @@ class AddOrganisationApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); $this->assertDbRecordExists('Organisations', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddOrganisationNotAllowedAsRegularUser(): void @@ -73,7 +69,5 @@ class AddOrganisationApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('Organisations', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index 12b62d1..9e3ae3c 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -12,7 +12,6 @@ use App\Test\Helper\ApiTestTrait; class DeleteOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/delete'; @@ -33,8 +32,6 @@ class DeleteOrganisationApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteOrganisationNotAllowedAsRegularUser(): void @@ -45,7 +42,5 @@ class DeleteOrganisationApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php index 75c032c..4587808 100644 --- a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\OrganisationsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class EditOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/edit'; @@ -45,8 +43,6 @@ class EditOrganisationApiTest extends TestCase 'name' => 'Test Organisation 4321', ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testEditOrganisationNotAllowedAsRegularUser(): void @@ -69,7 +65,5 @@ class EditOrganisationApiTest extends TestCase 'name' => 'Test Organisation 1234' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } } diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php index 709883a..23a68c9 100644 --- a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\OrganisationsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/index'; @@ -33,7 +31,5 @@ class IndexOrganisationApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_A_ID)); $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_B_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php index c79109a..d22f31a 100644 --- a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\OrganisationsFixture; @@ -13,7 +12,6 @@ use App\Test\Helper\ApiTestTrait; class TagOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/tag'; @@ -49,8 +47,6 @@ class TagOrganisationApiTest extends TestCase 'fk_model' => 'Organisations' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } public function testTagOrganisationNotAllowedAsRegularUser(): void @@ -74,7 +70,5 @@ class TagOrganisationApiTest extends TestCase 'fk_model' => 'Organisations' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } } diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php index 1aff58a..6cbb55d 100644 --- a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\OrganisationsFixture; @@ -13,7 +12,6 @@ use App\Test\Helper\ApiTestTrait; class UntagOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/untag'; @@ -49,8 +47,6 @@ class UntagOrganisationApiTest extends TestCase 'fk_model' => 'Organisations' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } public function testUntagOrganisationNotAllowedAsRegularUser(): void @@ -74,7 +70,5 @@ class UntagOrganisationApiTest extends TestCase 'fk_model' => 'Organisations' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'post'); } } diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php index 7170e98..5e51698 100644 --- a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -4,16 +4,13 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\OrganisationsFixture; -use App\Test\Fixture\UsersFixture; use App\Test\Helper\ApiTestTrait; class ViewOrganisationApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/organisations/view'; @@ -36,7 +33,5 @@ class ViewOrganisationApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains('"name": "Organisation A"'); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php index 0b0c8ab..2843687 100644 --- a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\UsersFixture; @@ -13,7 +12,6 @@ use App\Test\Helper\ApiTestTrait; class AddSharingGroupApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/sharingGroups/add'; @@ -51,8 +49,6 @@ class AddSharingGroupApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); $this->assertDbRecordExists('SharingGroups', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddSharingGroupNotAllowedAsRegularUser(): void @@ -78,7 +74,5 @@ class AddSharingGroupApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('SharingGroups', ['uuid' => $uuid]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php index 36379d4..b9d4a0c 100644 --- a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\SharingGroupsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class DeleteSharingGroupApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/sharingGroups/delete'; @@ -34,8 +32,6 @@ class DeleteSharingGroupApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteSharingGroupNotAllowedAsRegularUser(): void @@ -46,7 +42,5 @@ class DeleteSharingGroupApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php index 7de711c..9f861e8 100644 --- a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php @@ -4,16 +4,13 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; -use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\SharingGroupsFixture; use App\Test\Helper\ApiTestTrait; class EditSharingGroupApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/sharingGroups/edit'; @@ -47,8 +44,6 @@ class EditSharingGroupApiTest extends TestCase 'name' => 'Test Sharing Group 4321', ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testEditSharingGroupNotAllowedAsRegularUser(): void @@ -71,7 +66,5 @@ class EditSharingGroupApiTest extends TestCase 'name' => 'Test Sharing Group 1234' ] ); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } } diff --git a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php index 82f7255..2cbded2 100644 --- a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php +++ b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\SharingGroupsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexSharingGroupsApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/sharingGroups/index'; @@ -34,7 +32,5 @@ class IndexSharingGroupsApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_B_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php index 3944122..0bbfbf5 100644 --- a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\SharingGroupsFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class ViewSharingGroupApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/sharingGroups/view'; @@ -34,7 +32,5 @@ class ViewSharingGroupApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/tests/TestCase/Api/Tags/IndexTagsApiTest.php b/tests/TestCase/Api/Tags/IndexTagsApiTest.php index 330e93f..962750f 100644 --- a/tests/TestCase/Api/Tags/IndexTagsApiTest.php +++ b/tests/TestCase/Api/Tags/IndexTagsApiTest.php @@ -4,15 +4,12 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; -use App\Test\Fixture\UsersFixture; use App\Test\Helper\ApiTestTrait; class IndexTagsApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/tags/index'; @@ -35,7 +32,5 @@ class IndexTagsApiTest extends TestCase $this->assertResponseContains('"name": "red"'); $this->assertResponseContains('"name": "green"'); $this->assertResponseContains('"name": "blue"'); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index 396307e..6741b9b 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -14,7 +13,6 @@ use App\Test\Helper\ApiTestTrait; class AddUserApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/add'; @@ -45,8 +43,6 @@ class AddUserApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains('"username": "test"'); $this->assertDbRecordExists('Users', ['username' => 'test']); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } public function testAddUserNotAllowedAsRegularUser(): void @@ -66,7 +62,5 @@ class AddUserApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordNotExists('Users', ['username' => 'test']); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } } diff --git a/tests/TestCase/Api/Users/ChangePasswordApiTest.php b/tests/TestCase/Api/Users/ChangePasswordApiTest.php index f1c1b82..fca8339 100644 --- a/tests/TestCase/Api/Users/ChangePasswordApiTest.php +++ b/tests/TestCase/Api/Users/ChangePasswordApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -16,7 +15,6 @@ use Cake\Controller\ComponentRegistry; class ChangePasswordApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/edit'; @@ -59,8 +57,6 @@ class ChangePasswordApiTest extends TestCase ); $this->assertResponseOk(); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); // Test new password with form login $request = new ServerRequest([ diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 48f9069..4e99963 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -4,17 +4,13 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; -use App\Test\Fixture\OrganisationsFixture; -use App\Test\Fixture\RolesFixture; use App\Test\Helper\ApiTestTrait; class DeleteUserApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/delete'; @@ -35,8 +31,6 @@ class DeleteUserApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('Users', ['id' => UsersFixture::USER_REGULAR_USER_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteUserNotAllowedAsRegularUser(): void @@ -47,7 +41,5 @@ class DeleteUserApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('Users', ['id' => UsersFixture::USER_ORG_ADMIN_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } } diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index d52aea9..93a0df8 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -4,17 +4,14 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; use App\Test\Fixture\RolesFixture; use App\Test\Helper\ApiTestTrait; -use Authentication\PasswordHasher\DefaultPasswordHasher; class EditUserApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/edit'; @@ -44,8 +41,6 @@ class EditUserApiTest extends TestCase 'id' => UsersFixture::USER_REGULAR_USER_ID, 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID ]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'put'); } public function testEditRoleNotAllowedAsRegularUser(): void @@ -63,7 +58,5 @@ class EditUserApiTest extends TestCase 'id' => UsersFixture::USER_REGULAR_USER_ID, 'role_id' => RolesFixture::ROLE_ADMIN_ID ]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'put'); } } diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php index 7d96936..7303224 100644 --- a/tests/TestCase/Api/Users/IndexUsersApiTest.php +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class IndexUsersApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/index'; @@ -32,7 +30,5 @@ class IndexUsersApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } } diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php index d1d2c54..576f2f8 100644 --- a/tests/TestCase/Api/Users/ViewUserApiTest.php +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; -use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -12,7 +11,6 @@ use App\Test\Helper\ApiTestTrait; class ViewUserApiTest extends TestCase { - use IntegrationTestTrait; use ApiTestTrait; protected const ENDPOINT = '/api/v1/users/view'; @@ -32,8 +30,6 @@ class ViewUserApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); } public function testViewUserById(): void @@ -44,7 +40,5 @@ class ViewUserApiTest extends TestCase $this->assertResponseOk(); $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_REGULAR_USER_USERNAME)); - // TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url); } } diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index 076af9f..b584bfc 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -70,7 +70,7 @@ paths: summary: "Add individual" operationId: addIndividual tags: - - Users + - Individuals requestBody: $ref: "#/components/requestBodies/CreateIndividualRequest" responses: @@ -1517,11 +1517,11 @@ components: uuid: $ref: "#/components/schemas/UUID" email: - $ref: "#/components/schemas/IndividualLastName" + $ref: "#/components/schemas/Email" first_name: $ref: "#/components/schemas/IndividualFirstName" last_name: - type: boolean + $ref: "#/components/schemas/IndividualLastName" position: $ref: "#/components/schemas/IndividualPosition" From 862ea58e490cedb64708288d19efc7df315514c6 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 15:18:29 +0100 Subject: [PATCH 30/36] fix: assertions are already executed --- .../TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php index 4bf0174..b089c91 100644 --- a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php @@ -32,8 +32,6 @@ class DeleteEncryptionKeyApiTest extends TestCase $this->assertResponseOk(); $this->assertDbRecordNotExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID]); - //TODO: $this->assertRequestMatchesOpenApiSpec(); - $this->assertResponseMatchesOpenApiSpec($url, 'delete'); } public function testDeleteEncryptionKeyNotAllowedAsRegularUser(): void From b1f63f190c343efd3af3d66896c7c4df6644410c Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 16:08:50 +0100 Subject: [PATCH 31/36] chg: move openapi validator initialization to tests/bootstrap.php so its only parsed once. --- tests/Helper/ApiTestTrait.php | 24 ++++++++++--------- .../Api/Users/ChangePasswordApiTest.php | 2 +- tests/bootstrap.php | 8 +++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 2060063..a55268c 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace App\Test\Helper; use Cake\TestSuite\IntegrationTestTrait; -use Cake\Http\Exception\NotImplementedException; +use Cake\Core\Configure; use Cake\Http\ServerRequestFactory; use Cake\Http\ServerRequest; +use Cake\Http\Exception\NotImplementedException; use \League\OpenAPIValidation\PSR7\ValidatorBuilder; use \League\OpenAPIValidation\PSR7\RequestValidator; use \League\OpenAPIValidation\PSR7\ResponseValidator; use \League\OpenAPIValidation\PSR7\OperationAddress; -use PHPUnit\Exception as PHPUnitException; /** * Trait ApiTestTrait @@ -47,7 +47,7 @@ trait ApiTestTrait public function setUp(): void { parent::setUp(); - $this->initializeOpenApiValidator($_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'); + $this->initializeOpenApiValidator(); } public function setAuthToken(string $authToken): void @@ -83,16 +83,18 @@ trait ApiTestTrait } /** - * Parse the OpenAPI specification and create a validator + * Load OpenAPI specification validator * - * @param string $specFile * @return void */ - public function initializeOpenApiValidator(string $specFile): void + public function initializeOpenApiValidator(): void { - $this->_validator = (new ValidatorBuilder)->fromYamlFile($specFile); - $this->_requestValidator = $this->_validator->getRequestValidator(); - $this->_responseValidator = $this->_validator->getResponseValidator(); + if (!$this->_skipOpenApiValidations) { + $this->_validator = Configure::read('App.OpenAPIValidator'); + if ($this->_validator === null) { + throw new \Exception('OpenAPI validator is not configured'); + } + } } /** @@ -102,7 +104,7 @@ trait ApiTestTrait */ public function assertRequestMatchesOpenApiSpec(): void { - $this->_requestValidator->validate($this->_psrRequest); + $this->_validator->getRequestValidator()->validate($this->_psrRequest); } /** @@ -115,7 +117,7 @@ trait ApiTestTrait public function assertResponseMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void { $address = new OperationAddress($endpoint, $method); - $this->_responseValidator->validate($address, $this->_response); + $this->_validator->getResponseValidator()->validate($address, $this->_response); } /** diff --git a/tests/TestCase/Api/Users/ChangePasswordApiTest.php b/tests/TestCase/Api/Users/ChangePasswordApiTest.php index fca8339..8dcd517 100644 --- a/tests/TestCase/Api/Users/ChangePasswordApiTest.php +++ b/tests/TestCase/Api/Users/ChangePasswordApiTest.php @@ -36,7 +36,7 @@ class ChangePasswordApiTest extends TestCase public function setUp(): void { parent::setUp(); - $this->initializeOpenApiValidator($_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'); + $this->initializeOpenApiValidator(); $this->collection = new ComponentRegistry(); $this->auth = new FormAuthenticate($this->collection, [ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 34939c3..7523c28 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,6 +19,10 @@ declare(strict_types=1); use Cake\Core\Configure; use Cake\Datasource\ConnectionManager; use Migrations\TestSuite\Migrator; +use \League\OpenAPIValidation\PSR7\ValidatorBuilder; +use \League\OpenAPIValidation\PSR7\RequestValidator; +use \League\OpenAPIValidation\PSR7\ResponseValidator; +use \League\OpenAPIValidation\PSR7\OperationAddress; /** * Test runner bootstrap. @@ -64,3 +68,7 @@ if (!$_ENV['SKIP_DB_MIGRATIONS']) { } else { echo "[ * ] Skipping DB migrations ...\n"; } + +$specFile = $_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'; +// Initialize OpenAPI spec validator +Configure::write('App.OpenAPIValidator', (new ValidatorBuilder)->fromYamlFile($specFile)); From 01b4bc9bac7d0804ef26e526e92fa8f9bcfc59ab Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 16:13:37 +0100 Subject: [PATCH 32/36] chg: remove todo section --- tests/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/README.md b/tests/README.md index d5f4385..434f84a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -121,8 +121,3 @@ The default OpenAPI spec path is set in `phpunit.xml` as a environment variablea $ export XDEBUG_CONFIG="idekey=IDEKEY" $ phpunit ``` - -## TODO -- [ ] Validate API requests against the OpenAPI spec -- [ ] Validate response content matches / implement _seeResponseContainsJson()_ or equivalent -- [ ] Parse OpenAPI spec only once per test run, currently re-loading in every _TestCase::setUp()_ \ No newline at end of file From 25b7d167f1d0d8a335e727a876764c0c160f2a9e Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 19 Jan 2022 16:22:44 +0100 Subject: [PATCH 33/36] chg: remove the /api/v1 prefix for api endpoints --- config/routes.php | 35 +++----- .../Api/AuthKeys/AddAuthKeyApiTest.php | 2 +- .../Api/AuthKeys/DeleteAuthKeyApiTest.php | 2 +- .../Api/AuthKeys/IndexAuthKeysApiTest.php | 2 +- tests/TestCase/Api/Broods/AddBroodApiTest.php | 2 +- .../Api/Broods/DeleteBroodApiTest.php | 2 +- .../TestCase/Api/Broods/EditBroodApiTest.php | 2 +- .../Api/Broods/IndexBroodsApiTest.php | 2 +- .../Api/Broods/TestBroodConnectionApiTest.php | 2 +- .../TestCase/Api/Broods/ViewBroodApiTest.php | 2 +- .../AddEncryptionKeyApiTest.php | 2 +- .../DeleteEncryptionKeyApiTest.php | 2 +- .../EditEncryptionKeyApiTest.php | 2 +- .../IndexEncryptionKeysApiTest.php | 2 +- .../ViewEncryptionKeyApiTest.php | 2 +- .../Api/Inbox/CreateInboxEntryApiTest.php | 2 +- .../TestCase/Api/Inbox/IndexInboxApiTest.php | 2 +- .../Api/Individuals/AddIndividualApiTest.php | 2 +- .../Individuals/DeleteIndividualApiTest.php | 2 +- .../Api/Individuals/EditIndividualApiTest.php | 2 +- .../Individuals/IndexIndividualsApiTest.php | 2 +- .../Api/Individuals/ViewIndividualApiTest.php | 2 +- .../Organisations/AddOrganisationApiTest.php | 2 +- .../DeleteOrganisationApiTest.php | 2 +- .../Organisations/EditOrganisationApiTest.php | 2 +- .../IndexOrganisationsApiTest.php | 2 +- .../Organisations/TagOrganisationApiTest.php | 2 +- .../UntagOrganisationApiTest.php | 2 +- .../Organisations/ViewOrganisationApiTest.php | 2 +- .../SharingGroups/AddSharingGroupApiTest.php | 2 +- .../DeleteSharingGroupApiTest.php | 2 +- .../SharingGroups/EditSharingGroupApiTest.php | 2 +- .../IndexSharingGroupsApiTest.php | 2 +- .../SharingGroups/ViewSharingGroupApiTest.php | 2 +- tests/TestCase/Api/Tags/IndexTagsApiTest.php | 2 +- tests/TestCase/Api/Users/AddUserApiTest.php | 2 +- .../Api/Users/ChangePasswordApiTest.php | 2 +- .../TestCase/Api/Users/DeleteUserApiTest.php | 2 +- tests/TestCase/Api/Users/EditUserApiTest.php | 2 +- .../TestCase/Api/Users/IndexUsersApiTest.php | 2 +- tests/TestCase/Api/Users/ViewUserApiTest.php | 2 +- webroot/docs/openapi.yaml | 90 +++++++++---------- 42 files changed, 96 insertions(+), 109 deletions(-) diff --git a/config/routes.php b/config/routes.php index d51121a..ac1ab26 100644 --- a/config/routes.php +++ b/config/routes.php @@ -92,27 +92,14 @@ $routes->prefix('Open', function (RouteBuilder $routes) { $routes->fallbacks(DashedRoute::class); }); -// API routes -$routes->scope('/api', function (RouteBuilder $routes) { - // $routes->applyMiddleware('ratelimit', 'auth.api'); - $routes->scope('/v1', function (RouteBuilder $routes) { - // $routes->applyMiddleware('v1compat'); - $routes->setExtensions(['json']); - - // Generic API route - $routes->connect('/{controller}/{action}/*'); - - // Tags plugin routes - $routes->plugin( - 'tags', - ['path' => '/tags'], - function ($routes) { - $routes->setRouteClass(DashedRoute::class); - $routes->connect( - '/{action}/*', - ['controller' => 'Tags'] - ); - } - ); - }); -}); +/* + * If you need a different set of middleware or none at all, + * open new scope and define routes there. + * + * ``` + * $routes->scope('/api', function (RouteBuilder $builder) { + * // No $builder->applyMiddleware() here. + * // Connect API actions here. + * }); + * ``` + */ diff --git a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php index 32a79c4..ca305e8 100644 --- a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php +++ b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php @@ -13,7 +13,7 @@ class AddAuthKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/authKeys/add'; + protected const ENDPOINT = '/authKeys/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php index 539ab29..a621f37 100644 --- a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php +++ b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php @@ -12,7 +12,7 @@ class DeleteAuthKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/authKeys/delete'; + protected const ENDPOINT = '/authKeys/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php index 43a3390..0712480 100644 --- a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php +++ b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php @@ -12,7 +12,7 @@ class IndexAuthKeysApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/authKeys/index'; + protected const ENDPOINT = '/authKeys/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/AddBroodApiTest.php b/tests/TestCase/Api/Broods/AddBroodApiTest.php index 3da884c..f064f61 100644 --- a/tests/TestCase/Api/Broods/AddBroodApiTest.php +++ b/tests/TestCase/Api/Broods/AddBroodApiTest.php @@ -13,7 +13,7 @@ class AddBroodApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/broods/add'; + protected const ENDPOINT = '/broods/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/DeleteBroodApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php index c54e47a..420bf01 100644 --- a/tests/TestCase/Api/Broods/DeleteBroodApiTest.php +++ b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php @@ -13,7 +13,7 @@ class DeleteBroodApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/broods/delete'; + protected const ENDPOINT = '/broods/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/EditBroodApiTest.php b/tests/TestCase/Api/Broods/EditBroodApiTest.php index 9bf3077..ad5d70b 100644 --- a/tests/TestCase/Api/Broods/EditBroodApiTest.php +++ b/tests/TestCase/Api/Broods/EditBroodApiTest.php @@ -14,7 +14,7 @@ class EditBroodApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/broods/edit'; + protected const ENDPOINT = '/broods/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php index 598898d..d70bc53 100644 --- a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php +++ b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php @@ -13,7 +13,7 @@ class IndexBroodsApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/index'; + protected const ENDPOINT = '/users/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php index 31c2d48..ee1117f 100644 --- a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php +++ b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php @@ -16,7 +16,7 @@ class TestBroodConnectionApiTest extends TestCase use ApiTestTrait; use WireMockTestTrait; - protected const ENDPOINT = '/api/v1/broods/testConnection'; + protected const ENDPOINT = '/broods/testConnection'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Broods/ViewBroodApiTest.php b/tests/TestCase/Api/Broods/ViewBroodApiTest.php index c357d90..bd9e5a7 100644 --- a/tests/TestCase/Api/Broods/ViewBroodApiTest.php +++ b/tests/TestCase/Api/Broods/ViewBroodApiTest.php @@ -13,7 +13,7 @@ class ViewBroodApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/broods/view'; + protected const ENDPOINT = '/broods/view'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php index 1adf5d9..00cc377 100644 --- a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php @@ -14,7 +14,7 @@ class AddEncryptionKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/encryptionKeys/add'; + protected const ENDPOINT = '/encryptionKeys/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php index b089c91..6ae8143 100644 --- a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php @@ -13,7 +13,7 @@ class DeleteEncryptionKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/encryptionKeys/delete'; + protected const ENDPOINT = '/encryptionKeys/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php index 300a969..2636fc1 100644 --- a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php @@ -13,7 +13,7 @@ class EditEncryptionKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/encryptionKeys/edit'; + protected const ENDPOINT = '/encryptionKeys/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php index 9aec4c5..844336d 100644 --- a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php @@ -13,7 +13,7 @@ class IndexEncryptionKeysApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/encryptionKeys/index'; + protected const ENDPOINT = '/encryptionKeys/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php index 39c6519..de324fb 100644 --- a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php +++ b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php @@ -13,7 +13,7 @@ class ViewEncryptionKeyApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/encryptionKeys/view'; + protected const ENDPOINT = '/encryptionKeys/view'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php index 9c34aa0..8434d62 100644 --- a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -12,7 +12,7 @@ class CreateInboxEntryApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/inbox/createEntry'; + protected const ENDPOINT = '/inbox/createEntry'; protected $fixtures = [ 'app.Inbox', diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php index da99710..ae9c039 100644 --- a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -13,7 +13,7 @@ class IndexInboxApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/inbox/index'; + protected const ENDPOINT = '/inbox/index'; protected $fixtures = [ 'app.Inbox', diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php index cced4e4..fcff319 100644 --- a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -12,7 +12,7 @@ class AddIndividualApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/individuals/add'; + protected const ENDPOINT = '/individuals/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php index 0768b59..e5657aa 100644 --- a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -13,7 +13,7 @@ class DeleteIndividualApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/individuals/delete'; + protected const ENDPOINT = '/individuals/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php index cdff2d9..c888bba 100644 --- a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -13,7 +13,7 @@ class EditIndividualApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/individuals/edit'; + protected const ENDPOINT = '/individuals/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php index 29da85f..e5c92ce 100644 --- a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -13,7 +13,7 @@ class IndexIndividualsApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/individuals/index'; + protected const ENDPOINT = '/individuals/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php index 8589f1f..d4b94d9 100644 --- a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php +++ b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php @@ -13,7 +13,7 @@ class ViewIndividualApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/individuals/view'; + protected const ENDPOINT = '/individuals/view'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php index bde14a0..5a47554 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -12,7 +12,7 @@ class AddOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/add'; + protected const ENDPOINT = '/organisations/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index 9e3ae3c..efdaa5c 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -14,7 +14,7 @@ class DeleteOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/delete'; + protected const ENDPOINT = '/organisations/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php index 4587808..6d14f3c 100644 --- a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -13,7 +13,7 @@ class EditOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/edit'; + protected const ENDPOINT = '/organisations/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php index 23a68c9..a22e0f4 100644 --- a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -13,7 +13,7 @@ class IndexOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/index'; + protected const ENDPOINT = '/organisations/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php index d22f31a..f8bd194 100644 --- a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -14,7 +14,7 @@ class TagOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/tag'; + protected const ENDPOINT = '/organisations/tag'; protected $fixtures = [ 'app.TagsTags', diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php index 6cbb55d..59f1bea 100644 --- a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -14,7 +14,7 @@ class UntagOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/untag'; + protected const ENDPOINT = '/organisations/untag'; protected $fixtures = [ 'app.TagsTags', diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php index 5e51698..a9a728b 100644 --- a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -13,7 +13,7 @@ class ViewOrganisationApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/organisations/view'; + protected const ENDPOINT = '/organisations/view'; protected $fixtures = [ 'app.TagsTags', diff --git a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php index 2843687..cbfebbb 100644 --- a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php @@ -14,7 +14,7 @@ class AddSharingGroupApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/sharingGroups/add'; + protected const ENDPOINT = '/sharingGroups/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php index b9d4a0c..e2d1dc5 100644 --- a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -13,7 +13,7 @@ class DeleteSharingGroupApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/sharingGroups/delete'; + protected const ENDPOINT = '/sharingGroups/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php index 9f861e8..07dff5b 100644 --- a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php @@ -13,7 +13,7 @@ class EditSharingGroupApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/sharingGroups/edit'; + protected const ENDPOINT = '/sharingGroups/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php index 2cbded2..5286af2 100644 --- a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php +++ b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php @@ -13,7 +13,7 @@ class IndexSharingGroupsApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/sharingGroups/index'; + protected const ENDPOINT = '/sharingGroups/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php index 0bbfbf5..06ceb93 100644 --- a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -13,7 +13,7 @@ class ViewSharingGroupApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/sharingGroups/view'; + protected const ENDPOINT = '/sharingGroups/view'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Tags/IndexTagsApiTest.php b/tests/TestCase/Api/Tags/IndexTagsApiTest.php index 962750f..4b13b67 100644 --- a/tests/TestCase/Api/Tags/IndexTagsApiTest.php +++ b/tests/TestCase/Api/Tags/IndexTagsApiTest.php @@ -12,7 +12,7 @@ class IndexTagsApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/tags/index'; + protected const ENDPOINT = '/tags/index'; protected $fixtures = [ 'app.TagsTags', diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index 6741b9b..3437d29 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -15,7 +15,7 @@ class AddUserApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/add'; + protected const ENDPOINT = '/users/add'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Users/ChangePasswordApiTest.php b/tests/TestCase/Api/Users/ChangePasswordApiTest.php index 8dcd517..201cb74 100644 --- a/tests/TestCase/Api/Users/ChangePasswordApiTest.php +++ b/tests/TestCase/Api/Users/ChangePasswordApiTest.php @@ -17,7 +17,7 @@ class ChangePasswordApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/edit'; + protected const ENDPOINT = '/users/edit'; /** @var \Cake\Auth\FormAuthenticate */ protected $auth; diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 4e99963..9288b9a 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -13,7 +13,7 @@ class DeleteUserApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/delete'; + protected const ENDPOINT = '/users/delete'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 93a0df8..dd685b9 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -14,7 +14,7 @@ class EditUserApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/edit'; + protected const ENDPOINT = '/users/edit'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php index 7303224..c30462b 100644 --- a/tests/TestCase/Api/Users/IndexUsersApiTest.php +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -13,7 +13,7 @@ class IndexUsersApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/index'; + protected const ENDPOINT = '/users/index'; protected $fixtures = [ 'app.Organisations', diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php index 576f2f8..e3ab847 100644 --- a/tests/TestCase/Api/Users/ViewUserApiTest.php +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -13,7 +13,7 @@ class ViewUserApiTest extends TestCase { use ApiTestTrait; - protected const ENDPOINT = '/api/v1/users/view'; + protected const ENDPOINT = '/users/view'; protected $fixtures = [ 'app.Organisations', diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index b584bfc..8077ce7 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -29,7 +29,7 @@ tags: description: "Authkeys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries Cerebrate, add additional keys. Use the comment field to make identifying your keys easier." paths: - /api/v1/individuals/index: + /individuals/index: get: summary: "Get individuals list" operationId: getIndividuals @@ -47,7 +47,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/individuals/view/{individualId}: + /individuals/view/{individualId}: get: summary: "Get individual by ID" operationId: getIndividualById @@ -65,7 +65,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/individuals/add: + /individuals/add: post: summary: "Add individual" operationId: addIndividual @@ -83,7 +83,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/individuals/edit/{individualId}: + /individuals/edit/{individualId}: put: summary: "Edit individual" operationId: editIndividual @@ -103,7 +103,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/individuals/delete/{individualId}: + /individuals/delete/{individualId}: delete: summary: "Delete individual by ID" operationId: deleteIndividualById @@ -121,7 +121,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/index: + /users/index: get: summary: "Get users list" operationId: getUsers @@ -139,7 +139,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/view: + /users/view: get: summary: "Get information about the current user" operationId: viewUserMe @@ -155,7 +155,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/view/{userId}: + /users/view/{userId}: get: summary: "Get information of a user by ID" operationId: viewUserById @@ -173,7 +173,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/add: + /users/add: post: summary: "Add user" operationId: addUser @@ -191,7 +191,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/edit: + /users/edit: put: summary: "Edit current user" operationId: editUser @@ -209,7 +209,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/edit/{userId}: + /users/edit/{userId}: put: summary: "Edit current user" operationId: editUserById @@ -229,7 +229,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/users/delete/{userId}: + /users/delete/{userId}: delete: summary: "Delete user by ID" operationId: deleteUserById @@ -247,7 +247,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/add: + /organisations/add: post: summary: "Add organisation" operationId: addOrganisation @@ -265,7 +265,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/edit/{organisationId}: + /organisations/edit/{organisationId}: put: summary: "Edit organisation" operationId: editOrganisation @@ -285,7 +285,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/index: + /organisations/index: get: summary: "Get organisations" operationId: getOrganisations @@ -303,7 +303,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/view/{organisationId}: + /organisations/view/{organisationId}: get: summary: "View organisation by ID" operationId: getOrganisationById @@ -321,7 +321,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/delete/{organisationId}: + /organisations/delete/{organisationId}: delete: summary: "Delete organisation by ID" operationId: deleteOrganisationById @@ -339,7 +339,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/tag/{organisationId}: + /organisations/tag/{organisationId}: post: summary: "Tag organisation by ID" operationId: tagOrganisationById @@ -359,7 +359,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/organisations/untag/{organisationId}: + /organisations/untag/{organisationId}: post: summary: "Remove organisation tag by ID" operationId: untagOrganisationById @@ -379,7 +379,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/tags/index: + /tags/index: get: summary: "Get tags list" operationId: getTags @@ -397,7 +397,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/inbox/index: + /inbox/index: get: summary: "Get inbox list" operationId: getinbox @@ -415,7 +415,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/inbox/createEntry/User/Registration: + /inbox/createEntry/User/Registration: post: summary: "Create user registration inbox entry" operationId: createInboxEntry @@ -433,7 +433,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/sharingGroups/index: + /sharingGroups/index: get: summary: "Get a sharing groups list" operationId: getSharingGroups @@ -451,7 +451,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/sharingGroups/add: + /sharingGroups/add: post: summary: "Add sharing group" operationId: addSharingGroup @@ -469,7 +469,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/sharingGroups/view/{sharingGroupId}: + /sharingGroups/view/{sharingGroupId}: get: summary: "Get sharing group by ID" operationId: getSharingGroupById @@ -487,7 +487,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/sharingGroups/delete/{sharingGroupId}: + /sharingGroups/delete/{sharingGroupId}: delete: summary: "Delete sharing group by ID" operationId: deleteSharingGroupById @@ -505,7 +505,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/sharingGroups/edit/{sharingGroupId}: + /sharingGroups/edit/{sharingGroupId}: put: summary: "Edit sharing group" operationId: editSharingGroup @@ -525,7 +525,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/index: + /broods/index: get: summary: "Get broods list" operationId: getBroods @@ -543,7 +543,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/view/{broodId}: + /broods/view/{broodId}: get: summary: "Get brood by ID" operationId: getBroodById @@ -561,7 +561,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/add: + /broods/add: post: summary: "Add brood" operationId: addBrood @@ -579,7 +579,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/edit/{broodId}: + /broods/edit/{broodId}: put: summary: "Edit brood" operationId: editBrood @@ -599,7 +599,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/delete/{broodId}: + /broods/delete/{broodId}: delete: summary: "Delete brood by ID" operationId: deleteBroodById @@ -617,7 +617,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/broods/testConnection/{broodId}: + /broods/testConnection/{broodId}: get: summary: "Test brood connection by ID" operationId: testBroodConnectionById @@ -636,7 +636,7 @@ paths: $ref: "#/components/responses/ApiErrorResponse" # EncryptionKeys - /api/v1/encryptionKeys/index: + /encryptionKeys/index: get: summary: "Get encryption keys list" operationId: getEncryptionKeys @@ -654,7 +654,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/encryptionKeys/view/{encryptionKeyId}: + /encryptionKeys/view/{encryptionKeyId}: get: summary: "Get encryption key by ID" operationId: getEncryptionKeyId @@ -672,7 +672,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/encryptionKeys/add: + /encryptionKeys/add: post: summary: "Add encryption key" operationId: addEncryptionKey @@ -690,7 +690,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/encryptionKeys/edit/{encryptionKeyId}: + /encryptionKeys/edit/{encryptionKeyId}: put: summary: "Edit encryption key" operationId: editEncryptionKey @@ -710,7 +710,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/encryptionKeys/delete/{encryptionKeyId}: + /encryptionKeys/delete/{encryptionKeyId}: delete: summary: "Delete encryption key by ID" operationId: deleteEncryptionKeyById @@ -729,7 +729,7 @@ paths: $ref: "#/components/responses/ApiErrorResponse" # AuthKeys - /api/v1/authKeys/index: + /authKeys/index: get: summary: "Get auth keys list" operationId: getAuthKeys @@ -747,7 +747,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/authKeys/add: + /authKeys/add: post: summary: "Add auth keys" operationId: addAuthKey @@ -765,7 +765,7 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" - /api/v1/authKeys/delete/{authKeyId}: + /authKeys/delete/{authKeyId}: delete: summary: "Delete auth key by ID" operationId: deleteAuthKeyById @@ -1374,7 +1374,7 @@ components: type: string url: type: string - example: "/api/v1/users" + example: "/users" code: type: integer example: 500 @@ -1391,7 +1391,7 @@ components: example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." url: type: string - example: "/api/v1/users" + example: "/users" code: type: integer example: 403 @@ -1408,7 +1408,7 @@ components: example: "You do not have permission to use this functionality." url: type: string - example: "/api/v1/users/index" + example: "/users/index" code: type: integer example: 405 @@ -1425,7 +1425,7 @@ components: example: "Invalid user" url: type: string - example: "/api/v1/users/users/view/1234" + example: "/users/users/view/1234" code: type: integer example: 404 From 6e4dc3a6cda038ffadad183f876f13ce68af0b9d Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Thu, 20 Jan 2022 16:23:48 +0100 Subject: [PATCH 34/36] add: github action test workflow --- .github/workflows/test.yml | 53 ++++++++++++++++++++++++++++++++++++ config/app_local.example.php | 10 +++---- 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..09c18d4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,53 @@ +name: test + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04] + php: ["7.4"] + steps: + - uses: actions/checkout@v2 + + - name: Create config files + run: | + cp ./config/app_local.example.php ./config/app_local.php + cp ./config/config.example.json ./config/config.json + + - name: Setup MariaDB + uses: getong/mariadb-action@v1.1 + with: + host port: 3306 + container port: 3306 + mysql database: "cerebrate_test" + mysql user: "cerebrate" + mysql password: "cerebrate" + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: pdo, pdo_mysql, mysqli, simplexml + + - name: Install dependencies + env: + php_version: ${{ matrix.php }} + run: | + sudo apt-get -y update + sudo apt-get install -y --no-install-recommends curl git zip unzip libicu-dev libxml2-dev + + - name: Run tests + run: | + composer install --no-progress --no-interaction + composer test + env: + DEBUG: true diff --git a/config/app_local.example.php b/config/app_local.example.php index 1ec0f4a..ed0c5ec 100644 --- a/config/app_local.example.php +++ b/config/app_local.example.php @@ -73,12 +73,10 @@ return [ * The test connection is used during the test suite. */ 'test' => [ - 'host' => 'localhost', - //'port' => 'non_standard_port_number', - 'username' => 'my_app', - 'password' => 'secret', - 'database' => 'test_myapp', - //'schema' => 'myapp', + 'host' => '127.0.0.1', + 'username' => 'cerebrate', + 'password' => 'cerebrate', + 'database' => 'cerebrate_test', ], ], From b2ffc822e97f817754b26137edf97a7b137d6ad1 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Thu, 20 Jan 2022 16:29:31 +0100 Subject: [PATCH 35/36] fix: run wiremock in background --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f51bc6f..6d48735 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", "stan": "phpstan analyse src/", "test": [ - "sh ./tests/Helper/wiremock/start.sh", + "nohup sh ./tests/Helper/wiremock/start.sh >/dev/null 2>&1 &", "phpunit", "sh ./tests/Helper/wiremock/stop.sh" ] From 8d4eabf255cdb863d2d166201cbba77849d99320 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Thu, 20 Jan 2022 16:33:28 +0100 Subject: [PATCH 36/36] chg: minor improv --- .github/workflows/test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09c18d4..a2c763a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,10 +44,9 @@ jobs: run: | sudo apt-get -y update sudo apt-get install -y --no-install-recommends curl git zip unzip libicu-dev libxml2-dev + composer install --no-progress --no-interaction - name: Run tests - run: | - composer install --no-progress --no-interaction - composer test + run: composer test env: DEBUG: true