From d599204dcb1f2af2276ae01ac54eae28d2c6faf7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 12 Nov 2024 23:46:20 +0100 Subject: [PATCH] test(Integration): extend import test with xlsx data sets Signed-off-by: Arthur Schiwon --- lib/Service/ImportService.php | 10 +++- tests/integration/features/APIv1.feature | 48 ++++++++++++++++++ .../features/bootstrap/FeatureContext.php | 19 ++++++- .../resources/import-from-libreoffice.xlsx | Bin 0 -> 6498 bytes .../resources/import-from-ms365.xlsx | Bin 0 -> 13695 bytes 5 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 tests/integration/resources/import-from-libreoffice.xlsx create mode 100644 tests/integration/resources/import-from-ms365.xlsx diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index a30725019..cf701cdd6 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -285,8 +285,14 @@ public function import(?int $tableId, ?int $viewId, string $path, bool $createMi * @throws PermissionError */ private function loop(Worksheet $worksheet): void { - $firstRow = $worksheet->getRowIterator()->current(); - $secondRow = $worksheet->getRowIterator()->seek(2)->current(); + $rowIterator = $worksheet->getRowIterator(); + $firstRow = $rowIterator->current(); + $rowIterator->next(); + if (!$rowIterator->valid()) { + return; + } + $secondRow = $rowIterator->current(); + unset($rowIterator); $this->getColumns($firstRow, $secondRow); if (empty(array_filter($this->columns))) { diff --git a/tests/integration/features/APIv1.feature b/tests/integration/features/APIv1.feature index 333d225ee..a88c53d45 100644 --- a/tests/integration/features/APIv1.feature +++ b/tests/integration/features/APIv1.feature @@ -215,6 +215,54 @@ Feature: APIv1 | Val1 | Val2 | Val3 | 1 | 💙 | Ä | 2024-02-24 | false | | great | news | here | 99 | ⚠️ | Ö | 2016-06-01 | true | + @api1 @import + Scenario: Import xlsx table generated by 365 + Given user "participant1" uploads file "import-from-ms365.xlsx" + And table "Import test" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" + When user imports file "/import-from-ms365.xlsx" into last created table + Then import results have the following data + | found_columns_count | 8 | + | created_columns_count | 8 | + | inserted_rows_count | 2 | + | errors_count | 0 | + Then table has at least following typed columns + | Col1 | text | + | Col2 | text | + | Col3 | text | + | num | number | + | emoji | text | + | special | text | + | date | datetime | + | truth | selection | + Then table contains at least following rows + | Col1 | Col2 | Col3 | num | emoji | special | date | truth | + | Val1 | Val2 | Val3 | 1 | 💙 | Ä | 2024-02-24 00:00 | false | + | great | news | here | 99 | ⚠ | Ö | 2016-06-01 00:00 | true | + + @api1 @import + Scenario: Import xlsx table generated by LibreOffice + Given user "participant1" uploads file "import-from-libreoffice.xlsx" + And table "Import test" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" + When user imports file "/import-from-libreoffice.xlsx" into last created table + Then import results have the following data + | found_columns_count | 8 | + | created_columns_count | 8 | + | inserted_rows_count | 2 | + | errors_count | 0 | + Then table has at least following typed columns + | Col1 | text | + | Col2 | text | + | Col3 | text | + | num | number | + | emoji | text | + | special | text | + | date | datetime | + | truth | selection | + Then table contains at least following rows + | Col1 | Col2 | Col3 | num | emoji | special | date | truth | + | Val1 | Val2 | Val3 | 1 | 💙 | Ä | 2024-02-24 00:00 | false | + | great | news | here | 99 | ⚠ | Ö | 2016-06-01 00:00 | true | + @api1 Scenario: Create, edit and delete views Given table "View test" with emoji "👨🏻‍💻" exists for user "participant1" as "view-test" diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 725a93be1..0be0cec0e 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -13,6 +13,7 @@ use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Psr7\Utils; use PHPUnit\Framework\Assert; use PHPUnit\Framework\ExpectationFailedException; use Psr\Http\Message\ResponseInterface; @@ -64,6 +65,8 @@ class FeatureContext implements Context { private array $tableData = []; private array $viewData = []; + private $importColumnData = null; + // use CommandLineTrait; private CollectionManager $collectionManager; @@ -89,6 +92,7 @@ public function setUp() { * @AfterScenario */ public function cleanupUsers() { + $this->importColumnData = null; $this->collectionManager->cleanUp(); foreach ($this->createdUsers as $user) { $this->deleteUser($user); @@ -467,8 +471,21 @@ public function columnsForNodeV2(string $nodeType, string $nodeName, ?TableNode // (((((((((((((((((((((((((((( END API v2 ))))))))))))))))))))))))))))))))))) + /** + * @Given user :user uploads file :file + */ + public function uploadFile(string $user, string $file): void { + $this->setCurrentUser($user); + + $localFilePath = __DIR__ . '/../../resources/' . $file; + + $url = sprintf('%sremote.php/dav/files/%s/%s', $this->baseUrl, $user, $file); + $body = Utils::streamFor(fopen($localFilePath, 'rb')); + $this->sendRequestFullUrl('PUT', $url, $body); + Assert::assertEquals(201, $this->response->getStatusCode()); + } // IMPORT -------------------------- @@ -574,7 +591,7 @@ public function checkRowsExists(TableNode $table): void { $allValuesForColumn[] = $row[$indexForCol]; } foreach ($table->getColumn($key) as $item) { - Assert::assertTrue(in_array($item, $allValuesForColumn)); + Assert::assertTrue(in_array($item, $allValuesForColumn), sprintf('%s not in %s', $item, implode(', ', $allValuesForColumn))); } } } diff --git a/tests/integration/resources/import-from-libreoffice.xlsx b/tests/integration/resources/import-from-libreoffice.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6cdff60f7f2d666e22dc5b2b958c7aff15ef27ca GIT binary patch literal 6498 zcmaJ_1z1$?@?N^TL20C2kWOh>N>W;KX;@$vq(SMfm98ZfP$@yA5s^*-sii|wLJ$$S z;O{NKF#yS}RZW!&=Z*OOyxd+%83VaOl zaI%6xobGzNIN!bg=cSIU<9j~hrgaLdy*JxMp*lS4q__eQV<_989^XJs1p zx3Cddd7b@^cHx$Nj27*?4`*9AgNJQjo4^kx&3i>Bf;W|PogFdUb*UJ=N`o&)jy1+; zO#PtL!~3`KdK{=y^5c1{pPGB$iQ$QQ(v)W@4aQv3O@I-&cO`67=}rfozGaW;ozYWg>#ez4)l3Ft)6Yqm~XxJ$OzF8`B zMBE^e5XZI*zDj&2@?k-h^BbP9(9%~-2_ge_kH~gH`?4|04W`ZG()Rs>>~(tMje3eP z9BW5Qfm4sWlf;*F?!8{X9+A9t!WkrEU!=|@KRB_^x?mIZ(PWGX>A>=+R_<a7Ea_IL<)@Ag zn)}AEb$yV|*f~%Llti;}v|QBL6hgv(wjU^-eW&KjFvF|DBnHKLvdj1OlKW#bg2nA@ zSvI*BtVUv=FwT?XTNLHzyV2k?crTAkMYoavw8);mD~09?`j zmNR7k;eY<`sBZUvgV#?KxX*x!}rc8p6-?}y~P=2)a2*E^WvzKubZREs2f+Q zW_q!6uvglxx%3+Gp3h>#KT1W)Uokqi`Q0Ug)kn!%@dX*}oH~c>K%d@*Bx1hBbsor3 zMF_*K{**qIQBDgAQy#XW*1{VYN|+f z7SHImIO&O^-8UvWe!s<5d82j^z8to1>~suCv|;Nh*F$2~n$A zW;)CaAodG+8GJ;X6Iju6!cfdK#7HKNLj>NUXatNBH1J3gOai`G{>VhuPSANsH3I-CtUUHvmEKvXYjj@XQ-`NPF({iRa(a)h6!aQo5 zuM{7ViYM5WmWTAVI`~7*b@UzNUJi1iWSkA5Jidbu1y{@j^KO!NfIK7MZuXfcDS=tN z6+vR@;O;q~q`}zUICEiR5QD9C#?Il+rC95Ehv7P;50l}z#_pW#I=zl7_n*>V(a{L@ z-_ncVpY(#le4N41>%`hIHiOIvlLySdCjLHLuTTx1KF;^;|!pY>)?dRFcpYShWWte>|xNIUa2+33JT1fO-mNs5ndi>8@Y&A9(~jv1MZPc+bc3vSxd)s<-F#lL3+WG2X}P@qJW%^(Xh!OR0jH-kqPms@`aG6-qsY z4NSSKh;&o0jV+}dqUb&w=Jr{LO+S9AGGH+ws`jOwI%1j_QpEuu#(a2(A2Eo%VMgr_ zzNNDrd3^4-^nG%G0@5TOdlGZ{l+ZiI zYPv9s5OD>lb2N`N4nww4vwB&nNGO=rDTM~;!8+AfZ)!!R=>CJl ztwpa(sT0lywp!(`V+QY~NvhJ-Cv!sVMd2U!KsqV2B~ef7eBYgYi_$@Am$o;E>q|Ze zbIh7i9S48?mPm7K1Gs1R`em;AgA(?!lrKHm^0q7Hqav&JZraXDR3B`;LCzhQ8L>0c zEm`mVw{8VNcliTb1G}3B)}ubP)(HeC*Nvq09G{Y1xSZ1;!F9HTbmQD#Ppvx$UjXZB zAsV&$R6C$@fei!p{0x)Za>lPIv(P}n#t)TSTgM^MiX{yq)i1p`pK%KCo-kRo*Wgc{ zV=WW5VWAA}AsGlbHiA*P?b*>S_Yv*0FuuZ%d^x5mgu7kge)0+xYxz$?Z%6t#_>`=q5C5(_=6D{P?&#BF#b63FxqIoD)F#xa>nVTo`a{ zPi%{7RXew9inMK z!27lg^oL=X3`-a{2`y*Eo8!-(V+PdIc67bg{>PG**p{_JYa>G7kKbEz^&{zOpISbk zmZLX|Ii@8*mC2C_+CD^kNjgs?->7W8jqlHJurZG9$B3DUwzA*1$9ZCN>9rLwCv{=c z_VZV1dIv0wLO}-rOc?*h9n`PL6$saEK3Svou21gUxa^y<9LP<}{(1h)B*Mb(5RGxSKq{EY0A=Z9k2~_lJ zYCqr{KlA9LZ_{!$5g6j4&-Fn;k%HS{eq8+2R1KVD2^$V>#gUHgdMvN7{TBW5UbF(J zG_F1y-6`3kQZ<*NOW7-1;N4SGnG9i(i}u9Y(Y79GRH8{yI7P8SBSFdMG*Nr$$9W*! z2xnd7 zPoQ0jpj`Rkp^sORo;9!LlKRd@W{a63F?^3Tkwny%Xg0~Ik?iA3QjF?~N>y=Z2{h3& zf)x(lFaXcc5iJ8BM$_?ghY+#@z{^y$u={wdBmK+U_QBMNj)?o`>ME4$!#ai2QIY5< znp{{OUmrA$-hC2IUM!~%GWyXXepn_>PETne)gPYg33z;t>OQCZ*`>27!EHKHk%8Zl z#q~#|J6+W_%dGHHT|#;-NxAe=Mcnsfmhduo?V{7d~<;Tt<RV0ak@1aEV;6=z#~0i2dJmTaGCY#I&-5NBD50B?m88Gx z+ub6Zc_UN$8d(~fB9f}2u+C>|`6#ByRuS~`BK%nb-aPR$WeX+af91w*OG= zXjy{b*lmzzxA*cK9=}C+<6fxD|aX6h3 znv!-^v=75*nMo@KSLMnkg3>-nWY^cJFMua!pIIf5zK!Eb)Crkhs)1XaAB3B(Oc`(9 zqCvrtALdNTZ1t)UoX2V(K(sUoBw)x&I}J$Zi=GQk<}n1?s0U7Wz}aIZ(iiBBVw3MP z?9(fW=0Y5^cQ%3ya`neJx@I%Xt@XtCk4~^OOx%1P3TPTWybrRyM7{2RVtgKm2VQa5 z1pePRjQrpIkG-V_*hUxT;oxfbs||`9WqXVKV2P@@8;2TdBPi?E+cp3x znFtRv{m1t%Y$De}jkaS+-R&M0d=5fy3e|9N5^@DG-13zcSTbNvVe;x$RWI}}6>TCV z0EgrHAi)6*ee-*8LK=KN6%CR!rnTK}%CY)*rgM1Tm-KY9cMWSEZdON_15Ko%e0DHD z4VC~E_1!#*+XCjQF~+$+`__K-*s~uf z=NDMbhZOp|E}5?RoQ@I>`Rdw=)B~;m33e08P9H&k-^#ITvw{yP`&c4z(1eXr4?KCwBsypgLU8t0*rzX- zlmV}DR%PcWq2B@4!eR0Yt91rRED9u0ya)SDIZuWsck59cdX=bm0^a9gZO_gNsZmzM zWY3gJh^)a>+oQT5AbU}{_?pX%*U1IS>^Fh9sDvCC+tF9zi_^r@U+PS73CXI>)ucW9 z*DWHxxkZ+4Zr3}Mlc?eJE1l0M+K*@@aEx$1xKB|_P}Ipk^ul{pSWAD=;idXxZ@sQ! z$44CWx|8Kg@s?5fHny~ild;|sD%c|QcF|P*M!DY*9XzaipzWE^Z~+;5t?h%OF%v9l zRcs^X^x*>eSXV_AVyt1wg-^mS1dLz%s29(ZNyO{4Wl^MHl7aNe)li}60&R(nbi`&+ z<<>ueu*xNW^Uq%FY2LA3`8i7@)NCiUHA&@~juzl&M<$Bq*}6Jk(|IiE8Vvevak@jJIYp)iQcFYE@Za69*56xf2 z_Lg)VRRBJC3h_TJh(uRCT4w?MG>Nt>#idhKiHA6y@Zk?t~ahd6-IW- z>@Rk@zbSOCMrbp?_ZypwiPkN0TUwVsWJaZ#ih?)RJ6 z-m5y?*BS3Y%}!Gy>S5eUK^GK}+w@R*R6_Jch$g}J6b5c*PcJ`D)MNTgrz-VYhCh!6 zkCr9OJsQK`lkH9HkRi@d^?Yl=>;^!*h{+>SI}i&aYm%wKe95ak#w~fa&PUr(CH>8t z6&{Q9E{?e!Nu&Va8Z!GC)(Fqqj!^MW1X3ohFDx{GLl#kt*29T zRXqXS$`W5M`ByezOR?X)$;CvQXCcjzn!Kb>@D9G#KjLQgT)&W$G)0fpeRDdCRVdz9 zFWCCQPt4_2*a-Xeo^fPgrbDmtP!aQb&rndw0l&rQoAT?m82#V&zoqFv6Nc%I&&5D2B82z?J!haU9Yvpfa zxjOvKF8{8af4bi!s%swqZC#`{-2ct$e|q1fga6*S%d5lR7yl2n{Tbk9ZvJNx-a literal 0 HcmV?d00001 diff --git a/tests/integration/resources/import-from-ms365.xlsx b/tests/integration/resources/import-from-ms365.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..896b4276c445755efe28305ce81b702b5a9709fa GIT binary patch literal 13695 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