From 6ae89f2d58ccbd13450cce90bce565e6f1f654ff Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 9 Aug 2025 21:07:36 -0300 Subject: [PATCH 1/5] tests: improve coverage for utils, filters, content_fetcher; fix content_fetcher URL parsing to accept stripped paths; add error-path tests --- .coverage | Bin 0 -> 126976 bytes coverage.xml | 2322 ++++++++++++++++++++++++++++ struct_module/content_fetcher.py | 50 +- tests/test_content_fetcher_more.py | 160 ++ tests/test_filters_more.py | 93 ++ tests/test_utils_more.py | 65 + 6 files changed, 2665 insertions(+), 25 deletions(-) create mode 100644 .coverage create mode 100644 coverage.xml create mode 100644 tests/test_content_fetcher_more.py create mode 100644 tests/test_filters_more.py create mode 100644 tests/test_utils_more.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..4e72eef94de73dc11b7a9e3d32f51c6c12deb051 GIT binary patch literal 126976 zcmeFa2Y3`my8hc$J>6Yh(_w_j0!id7Au=XolXC`}BtRqs!bo5+7<=-_*O0#B6 zUQ=6Dy0UuH`pVkUzFJHpL>pOJs%e^y|CQi>>YoLOD1rZyM*LG~Yh_#NTz{_Sm?vxg zVt1O~%elbq9DUJQZ=Vpo$f~rLV@3X7z6J6vkZ*x}3;h4P1>!Rtt3|6;ByntQ<+62E zwVNuJS8bB7?x8csjG8^BboQvxlgE_GWu;wtDgO29RXVD4Q}xlM8>=>zu3WROs&vhU z6>FAP)~?yGsmD+l9P1On!=&%f-PHb4#@P0u{)~vw0YO7XZ7aKRNSzoznTj`Ni z+j=%^5PVu~)z(_M06VE#vucCbqO@!9_HLz{s#aEQs@kx;szx3~*EK7;@ovfExYeSt zkYuYGzEkC<<@o>TLEi=o4t$jfE3u>H)tgp`qhGeEa>Me~RW&_JE5mQNyc);3wN`%H zP1WnmYpYAwt=Z7!4!rr_yffH5c=Jje+=gn^tOwpGJ{oUsIOYwT*DphpuidYsY& zPrbLcI{32x<8$+G&gFscd(i1rO)k!;eAng;YmVAn6`uT_r3c>Gv*BxOsP0)Rz67p- zPEoVP;6kF|s;t^tbJRNg-da+*xwg7NFTu%QQh^sIRluwkB_$+#RKtZLnrF$fHMKP% z*auxS;o_gTbj13fzHY=_a_~hW?xJ<;N~>2k+%n?brSjz9YHobbl&i+5+$3(a(&}JC z9O?hnt4$m%j-hnfy6R;IUuz9lOidMT5O{&wY~|(+%f+oyBfi7(>Md29DpysN1#6`( z{NKKiaOqUoW{WW;KXvJZm%x&W5P7_nX|*USA_=?UG78RC@E_}-OGur`e`BL+)%vQ+ zn$4S(k^G-bQoZAVOR9S$Sc9+A%Q9PxD*CCf6KuDnA^;Y*B36rL%}89{5yF?m|BQpa zgjjOm7Wk(?@IOENjg_^lOaJxQ#bO+I7sG5Zq}fj$xHyC*72?%Q9zh!pIoqnC_1DAn z#sBn!4p}TYaLALYw)NUlxeji!v2x8O+_Las839VghAS$uapkh=&9x2JuaFIwr-(fV zj#uvBfL3jft-3XAj@x^BbxqA0gjEr?Y7S9LOS`PDtPw6$)1`D-b@jTc$_+9$;2jfc z@R@6BN*j*hU!&591N*4iCibyvOP7XYQo%*o{A}EG>iz<(UthUlMG&t<7?yj5_luiT ze2z2`ys@+kZps?mqW`o{wbNj&a4fa|vW;jHvDfvLE9C7iPD-Oa9=*EiUv?*>(P5Rf zRkdr@SCxuQ8V;u6!aBOLrgZtHDy&2-?bb#AKV1gmY%Qq}ucDXC#80_+39(xA=t1I} zgPT$M%#s=e_4SqNumedCx=8+M<$>#E8Vnb|b{bzNqU{>vpsp8uDaQO&>0U*os*9`ug%3fza>Dz};Q zh*RSfMW2XPN1O47_+swazq70Cmh4HkoprZfwGvjj`DZg{F2&pP|MD%6Z-IOZF&?jTv98T3-(0h)eBGL5Gb8=GryuYH%2vs<6K~ZLq)c zU$np4s`VS!VdUQu4C=>0HXP=RBJ8%yFWN2IscPMlqc>G<#C!!f(iyFP-Y#Y9t7b=Q zFR7{8gs6^P&T55S{){u(s4>O<8U_h&!aNGq8?e)9EwR%Mzv!#4*|2eQ?UI^Wd5R{q zzz$pdq8+ZTu0FB`$2Pq=cGc+@?Fz#^YcbwbT|bkWVUI<>#vXBaQ<`FDCBJlMn4|)i zBz8By5WDODi+0yA#Ck*Rl9g4p7>c?{ee0=Bu;*WP7vlckh^af!lsM8Q1=!!A|NA37 z_}UIFWmVM%jMK**DMz>B17^qY0lj{;4_LRRrdBkt|MMF7Ck`8fv)5o~v#_Pv-miMR zYc{N`7JGGnjlKS}G0kwwSKuz?FuhogBq}+y2u1Hn=_ZHj`s--G1rb4tA!g zHum{5Zi9o*f8Z!M#`z5U`x!Uv!TbAn@o2h*J^u{jJ9y7xsC#HG=C5(A|8mQs6=oRN z^UsK#2OsM}{xU!Es~+qUguFGiOP1hZr|H=9&xm#hA8ex?PNvx5&$#Lv#bni}IJ|KL zdn^3kU-fctjSfpZ|1U8e&2QyB)-9K z<-UD~z0z*Qe$RHW9@cACZ}WAt&aC{;{n490@_Y;ATOi*8`4-T$*^zh;^1qKM2ZuZr zq=No`Nf&(<`5*NEi>F26?S96GHwupj_5a(>jl|1;+5Z0B@2xg7Bk?Z3Y`6dXGrQ<# z?(*PfKdArT`e&ZWMveKe{r^_eBk>Nu?5iLAE4$^yNW8@_+o9_Jx0n%$clu?!`ltSX z^U0BT(XX;c{90=^HG-L-{`YrQqw~@DCwkKbk$C@Kw!5F`{}=x9yYQgj=S`MI;)ni> zBR%-q4lU(h`~L-VBJp0o-UkHz|JcuL;Ga0`M*V;P*FD~#|L^@Od;Mo)a_2?j6~98; z{&WA|nG%V&{l)uL{r~8!NW9xG-rK>>#DC^(aPav*(Eqo8mFwx>#Uu7JjqhKMH0=Ld zzsj-x%Pk8RG=G&qcaXmrzwW^{>K8|TX0$uFVH@pG|Cv{PqZptX6>Uk!MdF3_&y6UJ z4l6(ZU;dYWz6J6vkZ*x}3*=iM-vap-$hSbg1@bMBZ-IOZE@icXu1FVpIjo_K9> z;ugF%s~dvX!mN(hg49I3#uD8r{$u-V@v4=e@BgO%jDNp>gMWd4l7Ea`ELP?J%1^os_#QLUzb|z2J{|wzE(AMg;^mS-I z_ykHhd6PVeC7xQMeWmTz-WTKlOZ+O$-{*hpf9Zefe~735Z~1@rU-qB#pY|X3AM)?@ z@9=N&ulKL=FGXJ99RCdeWIyL8{hj_czt*qz*W&H@fB6>3w?MuH@-2{WfqV<(TOi*8 z`4-5xK)wa?Es$@4d<*C$m}y@x7B6kZOB?Z0BwkvJmsaAXrFdx}UYd)SX5yu(cqtSw zO~gxqc!`M@U%YtY#T74(c!`P^E?#W$!o-UuUQF>~h?j_X(ZvfDFAekmv)S4ndM@!l z_xI?x=nv}8=x^vB>EA{oktXIqdTyjNa$01UG1|&l53;fBaeimCX>@D!MQ4Ot;eO{$ z^Dgjv_>=wjktKM;e;)aQoBb>N^ZZkgAK2=z_m}ze`~m(bzcl)x-^}~i=U%<{jrX?q zviE>@n|HPMly|z9v9ItpdR5-x-gfUWZ!jzN%B(lMmY(ZTuY>!k`&Z*R_ch}y;{)Rj z_mA$w?j7!R?oMNXTjO3FxyY?^&vX~LGu?6SP(H_x~sLl8D5BXR8P5uIZj9<%7~HNi%pLk= z`k8u0->R?G7wS{>k$NA!BhDzJd+De2ZTbRzl>Uxh6-h*DBP%2GBNHP-BW00SB2Px{ zj$CJSHCh`^WPjuf<6+}A;|k+!BWE0AtTPrH)2*%MENiW`(3)zEG{;zd%x-3p=^FdZ zN~@#Q++yZl^HcL}^9A!!^LOS|=DF4_)@9b2)-x=|sP&EY0UN}+vpd-}?0j|-+sQVv zWo)*6ww<$&vDev)?dkStyPw_JZfSG&1N#Sim%U^^V?Su$Vt>RVd?+vDrSt^4gKnTp z=}daq|NFPJ%2MrrlXNBpN@-VUS8C^n>T>AwLUoCDxpr=-F4iv5&I#2;+Qr)0p}J7J zNINT3=W7>gXNKxL?R@Pwp*mYTPdg)2XKH6_r-$k{+L_vEp*mgrjdn^Sb)t51s7}*P z)J_W3sZb|`>J+G4s7{8;gz6;iWG&rDov5Xh+CPrmN5aqBzmA+uHizrdWCvLksvOxy z4iD8Cq?*hO)wyI1nHs7~$TBiMR1c6bq%2hXh^g&XO1nwBS-UJ$H$q<;svEQ$wM#;E zo%We_QK+uQ_q#AuS7}#k7li6c?JDiJO4S?M587uzHk0=0t9`20Xm@CLX;+2nHtkmJ z%23^kwO54dX6-iZa;55{+PB(=N@@3N4{0|vQV(c1hU#9dy)IODYxio`hUzZu5$$Sq zT)iu_zp6F$h1%!ZyGm(K*B_$Y8>*+YKWKL~Qcr7lhw4e~8SRcxJyt(L`(3CW)gIGs z57i^uliIDJdZ>Pac5|p6fVxQ?SFhK!*Fqkky&7_F?Uj(rwU@(pKZj#@NDJk&+H=~2 zjnp&R1EG3GdxiW_sr}EB3i6s#*vU)e*#oM6E_o(Y&tvT)j!1YDO0^TuB2R~_PSj2( zPlYO1zmxnvRDagq(4Gs`pR_+~2R`66?N8c)4|qj;O$!f9dj%i&hwyzbLm&9Om$B}^ z=e?x8tR48gm$2^1@O>{rJrSxGpdJg=GkD*9jns?UJX8 zBUFFGc7G1lF6{&D^+xJ%+Mm=uI=!u3AFlaG`&fG`R3B;|X@5})*WlgSHS8s=%}+%L zwWanYoSOH_?QNy%=V~`-7u&aJFa6Y~ z|7R{&XQB9g?Ns|Ct^5D%Tc}on<0Ag>h~^!5U!!%WYIVHyC)S;!W$~W>{pX#eCBsk1 zYB?!ee(H-O!r&ykoWFx2VP2kKL?r+xA+8xotISg^lUlsF z<|u`=n>Q+~*;=h|W9K#wr{>VvNFJ!w*w9 zbl9N^pBOq?;ox4Q6b>3ZQsKZsBNPr8I9%b7S;G`o^ct$LcdsD|`}R9TVZVyO3M={y zQdr)5V2Hg2C@d@QA7Zb53VW6HRoJsvAB8=7Rw#U}M{k8)JC`f$(zQ%s=Ptbzb|2DH zVW-YL6n5;?U15ig-4wpvp{v5u;w}nHN;@koF6pGOefN$E+ZK0F*rsiJh3~g%r?6FF zslt}6N))zeS*)B!NMHIXACQz9cG6_E~+W)VyOUjMuPmj1l|mH(dqn*S8C05|v- z`KS2_R0gc{=lc`=p?;ZP>NoKt-nZUI-W%RC-hDxA80Z**wRO;p_NfKAn%|{di~Il5_h9`yckZ_DlBT_MP@M_WAZn z_D*}Fz096%kF^Kc-R(BEXKU;$_8xnUJ;m;2H?WJ?X)MV$vsG*Xo5Y5(a@LL&GQ;}L z`q=u5^{n-fb*pu`b(WR2wpmA7i>ztZD66m4$!cNQ=05W?^Bwa=^D*-d^J?=v^F;Gl zv)ZgQXPINnfkB_Y-uTk^oAIjgd*dGCdgDUlR3olGqTjAxt^Za(RZr?i>+AGNeU3gs zAEsC6oseEas)z2TAJez!i}VS45519IO3$Ko^jLZnT}h9i)99geAnid*XdxXVMug=5 z{l~O``Y}Z#R3~9qg)|`!G1NLz02K*U42p)zhYEXs#3PQjKV0V$Py4Zva2;j*TJ8(??Yu_-FKlf3Dfp8QkM2@ zs0@6EZ$hObmGE__2-H`h(vSlEqLCunmr7~-iKcxXsvord+CM_Ij~LqDL-jpU3m=8* zJE%`W^{w_jInaImR{Ku-I9#_|+oye?6wcm$5_TZ*6Skg&T}`S%huulIcZ58m-iLR6 zPr}Y6`GM>uVb7iHgARM{WG~f7*zv@>_K~pr3bltk6z+2m%EiLu1=)?YVHcHr105zW z$k)(eVuO4E9d=*IXXH~7rZT`^kTA^w^%)5h8{{Lhi-d^{@*#BCWhEa#hiMM-KJ-oD zalQu~rftaI$a^GA>0pnalCVn*^*0iBVaYqtVS)$hO%f(}$lFj?hoA5kbeL=+Z(&`S zYy!VSt_in%6Kk(Hppc*o(+}h|@+!GJT=yz@kz5w4m&r@y(onsGZ7&Jci{x2yaj2dr z&yfp5^&EMbTo9^f$@Ap5jnp&bqEJ19BRxM0iJ`ik{EnOus$0oTq%KsqkXuPER5z1bNH$bAk=scoR5y~FNjg+F zkh@4KRM(LkN!W)aSED{N?8A~P$>k&-Zg~at@u9jL+a4FH%gA+PXQ(bF=aFMWbs_mJ z*%7J>$c1EksD4W>CdY*8d~yNV7OL~e6=Z9u&LQWMqeFEzIfrZs)tTfTQX8r>$nm5m zRHu_O$fi)8MouS3h3Zst8rc}CQ^={LI#ef;Q^!>T z+EAr&ENeoQf?5@-Bve(X5>U%S6(fI>67oiVW_t4&ygcSwUulq3qrMJ{~2U{sA{3+g{p>Z zA#)n3S~53ON0AybJ5<%=C^9Ql8^~5NBUJ15pG&5PY7JROriE%X*+8a*s*0>8lS8$F zRFO%cT25AwiJ@9XmXisgT1w6&<3hEFOd(@KwU8_#V?uQVSx62G)qHXUIW$yr$$T<8 zRCCB&GAdNF$s95=R5QtJGNO^1LWYNG20ndgsHQ_55~^urIvLzZO(6qAHI+;w0~)C* zq<^TUkeOsyBQ=8zX{4r+L5Mv@+(8bXGV?x8w_3?bb@HJBVix`t{H8BDr_Y5?}x zIaK}00MeH}38stTy$0}77`{uHX-cx2GFkt!oa zp(-c6N$W85u-gX{7p-mm8^mO)EzsotbbBUMgX9#DAN_-d$n z;ybhmRS(jWG!Iob(t|V$RaerDG!0c3(v=j3sx#?AnuMw&Ih4dg)eb+K{ZN&XcEk%+ z2`MFRsESDmaY9u@ib*t7tw|B#p=v=|6FXGRNDIP3)s!?NR;Zegro=@3Kj}`FYyKYp z3)KFN7cv7+8TT1C8J8Mo8g<4FquN+z%rV9zvr}fYGnyJ!WN+lN$h(o3QFU-n6gZY{#6>0l@kDo{VVDd2X&6s*3k2+nC%Og# zZp9N_0s*t)iOzw5SMfxrK)|YaqGKqqrb8fLR6Nl>6j;+P5U?qpC=CQ$iYH0}QAYak zL~$VCSUk}-5U?noXcKPMn>A(|uAdny6 z2^t6_Ab5g=Yhpa11p>(k9zQNz>clf}9-k2i zJV@v9>4Cs=bRM4;2s}pT@u`78tpSfu2?RQ%> zd3;15P{_gK!vldb4jvyC3al9#zUNsU9})^2+aZBK-3pHn4g`u5czjSGp3iDx%2{~4Um#G;!sC4dfnpXO?-RTS6$?CG5v)OB3yb%Lz*+9xiO0)> z%0~0UqxeiicRd2L#^U2`4v*H>lgWpT~oEgTjP+c|3?WqE3Ow zgLs2-qDOcgLrk;j90BgzwaJcu_M>Y{i&h&L#MI+4eNc!Mgat9d+#Hz;?zipPU^BdXtcJcu_a zcDsVdgLs2lx664vh&L#8yM)Jsc!Nr}i+MbVHz;(wh{uC?gF3ehc|3?WD04fX$Afr- zD!21^Jcu_aayy&HgLs1)w=-E>#2d79@%ua;L>mp&Z#*7E8wh-xAlfw49>jxaBT5l? zJcu?L>fm@hh&EVrDvt-zMng>;j|b5PYfj zBMJ}fhA`7mcfdrDLHq2?F%e-v$L_R6ctMT}MF&g-7cAURVFi@iW{a4D z4N-8wL`1>D%{5Gf6Hx7DCSnPwW-Akc1hlc1i6{c9-pGOwvZ0!Z2!dtnH!u-CKxeJD zMeM+?QD?wJxmRx=SW#M)I%L<`W0l}v;R(DD^b#0k*F%UKX4 zDvx9$MqpXt7fb{Q(Bd1Jhz_7diqQInSEC>R#Dp?Q%rcPo(2$(XJi3osqOrFAo{evb=X2SbH6DKiY z{Gjm@nQ(p3gz-#RzF0Pa3BL!88_$Hpo zcL$9a#e}tkhL2#v*FnREGhynWp~IMPbkGw+nXq%v;9g94IcU&eCX5_3a1awN4jM3! z2@3}enZ<;EgDQG4VcwwLy_j%rP~Uz`*fywN1rwevw(G-$VS~zhGvU^PdNEH^#D#otUsL9GgzFk4W|R!lf8s6|UAY!+11o(YcyHE+R$!3rv5!d*dy&6%)P zP_sfNd==ER855=|sE`Rq727ps!cIX2g-m!U=!*g-j1=U3!-R{1Vns|?DCirH3I7DS zF&3Do>oDP+SmwA)7$=B3Ot>ZpRoKEZLCj{tFF_V#!Yn~1P?BLhx<&E_Qdp*4p^z#jO zkNbB#$A8g%+`S9c`WK>ypLDmlYuttIG9aoxwXrB)q2Xh&$`LF6jk?G>lkajwbYtrjkN|_J*^U}iDj8Tm|vM6 zns1penZGyhH*ZCi{%_6G%(Qt7dh#pG1?E)qP;(Ia@*T}qrf2HL9^*6g=Ko|oV?1Kq zi6{M+7-tzL7{?j4#u|(Wn1vqwFr$yr#b|?S4KuPY@6hla9a%~mrdj+oOG(Ex+Z3~uv`n*Y8NQqo3Ex0I!%kD73flr&QF z8;7N&lbX14Q_@ONFT_&POHFu2N}8z&(?|(7wTVetO1h~D+ek?}HQ^g6>8B=)Bh_H2 zwuvKM zrLh_aM=9y7Mo(Ns(pn7!pOo}gqk9jQlICi3>&{ZrT@CguNLs9cAe53GYqaaYQqp9N5}9xk+ag8*XEiszyW+^J)R`@wf5s9DUYS1M5U$NwI5(}EK+=7wo%WL8yZ$eoZT$BUZTC1CD%#xu=GfYv3)GL zR-%goUn3FMZgRE6-S`fxBqG`+S4xb=Sh7kY!d-HO#JxDgMJqfhY+Jr(A5RM7 z76L^oJSnVO2-K+Xq%dzGP@=+VPPYRnC0`(_6DV$yil%MdV@OmLoeZrH%?S(+`2~P^Y7Xq~>JSi++h-b+&JSj|H z2-KnQq_BM;j!uaK8xD|x;3xTo|o)qRU1S(5-QrN!`C_&*#5dh?xCwWqY03lFv z!jmEh2=Op^h$lrD5CY{VJShT!5U4)kNf8Riu2nIr++Jq-XI1mEGCOjzuf)J=R z;Ykq^AjIf7J1K$!ZUxkx@T7C?N#OO?Xno2_aBz!jmFW2!Ub~o)ob{2-KSJ zq=*(mpwxsXM7$6Jl_oqPB8CtsG~o#mGlW2$2~UWqAq2`yctXStAy8$)6C!d5fg%&0 z5V1oD)R^#uh#o?q#DphA{15^aCOjb`h!7|+;Rz8#AjFtMo)A$)tU<8}Plz}o1Zquq zLPQcFP-?;xB9;h&N)w(C(L@Lon(%}OC_K#>Vg zh{z%YYD{=S#1<*=p(45nfeI6z5b;Hb<4Kw)Rs{mpCOok+T$AF7sz9LfgeO*nYmz)6 zB8}Jqs!(`B#2O(`gu)Xd+DL&lBHqZ)iSvYrI6|OygeOGM5dx(nJR!mk#D0v<5@PvpxLZFI-Ck_ub!>~=BP|*h!B|M>`4+=_nLPa0clkh|keNZq`%M&X4ptgi3 zRP;eH2~Vi#gIW@v2%-FLWQ4cVlx$fP=dk}D*Q|p zYgG7|LZ?xUtz-Yb-(UavYu)mm3|77DhBuANd+%0sex% zhvOf~_16}<#ehlLQ_IRIq?|QEwmvBEu1YF^r>z#zkfm&}h#so}9-M}EP zm)9Pn0ywG$zH&cs-*BHp&A^?=CtT#7fr^1+Ffw47I~Vl=!`(g@`QI8D1?qh3eB!)~ zT7f5>d!3sw^8ak-1ZO953M)}3Fx46D3~;)mN}!2jM)yYl5#5EX!XKg!MsJH=g>nC< zL=zY#ur|6lIx{*Jd4)2J6KEdg82kSfe;@S$&+8I2ujF(11XKuA zphBP(cR8_lBe(FD{UXK-++|;9Utph#+(M1L(q4ev!YI4H-OX-m7ucfL@)`RpdzC%S z9zb^CN_HMQnZ+?`U=3TuX0S1AFzbaXfo9CMezd-{-nU-I$bmpC$J3} zhDvLWHNhHYRbcc$E6cTrxf_)NZ(;nv6Q~rp(Y(|=3zY)Lnn#&c$S+JWN16RGhM=uk zU>e5v$S(X9wE|BY4;Z&%9LHz+U-eg!4R}DmRlia{Pd^#Y`Zw!q^hNp%0?9kN$3 z`<@mkwtghL6&pX0?-WNY^0i_;O1=p>LcSEkkC2x%bo|I}7=A>Jdh)s2;zyl)rT7Pf ze5?3-M)oM)9VOo@{>mr&6@OkxiQ>Pvq>+Flv3=z4ioMfYX?Y%@zJP=_v`A{vl7xfYCQ4Zz`^*ahtH?aC|+fRo~H? zIfFi>md}_))pxOG%%s0p%cswvPb!`^l|HU`>U8=<$kXU!ilYh^kT(L3h70P3!2ai6~_wb1&aM1^gP9$PtOgxo}RDR@#xu#xkJxVY;$_1VrJ9d zD7F|qL$PVm(-j*gJxy`MprPQ}M;r^hNj`WU)H@s^|McEy{w z(1wBZcywfL+)THr1M^tt7xs_Wy@)eV))OdkQdV}ikDW>qZBV$N;fKA zyq+E%@)BCDc+q;gKIFx8gW`pY=sLv<7SbaX&tE|0sCpd#y!mvEYM{B=33Qd(Xxv!3 zTrqC=Wg$OAS188KU#a-eF?6Zo(PL;;$cNG;ibu_*ixiJ|g)US){1tk*;-SOoe8q*xi6imxS|i8 zuDEvvou;^a1U(_--gK(svRCLN#l6bul#t8lM8!Qv(eaAAze~p{?$(KpRotZ$ovgTX zH##QdF7z1 z#kjJI6j%J6wpLu;jkZu+R!*BM?pa2gDelpeHdWlc2Q3V_8*Qn$TX)(d+ssjj&2b4nE#G$Zwj{jaE|*ef7ED0V!uPjR##`B6OoA51fvzuW)Bf5(3X)&CFr zxBJ)l7h<0OEPC!oq4xg>f4V=$Kg2KhJNm7B&)3m+{|t2if5K?~hrK(z>%5Cm2~g+l z^lH$1U*yg7#(6`%3RL|Uc`;N2>~%kP-*aC_|NSx41Ki+Vf;s!L?soLwE8RKn1oYqg zq9UNJ+r+h;ADk~y?f)0&1?LI$;BR)WKvlpgP7<~L>z!rJJZG{q3Vrwzk_ z=l^l^?dZ$Vr=ky_7k_p1g6Qd}^WPrb7_Gu+|7p>~qJzIFVzZ?YHJlc@W@1%3MS*{OJjxQ%T<&A@y% zg^gwdSx@xpo3kj>tlicp);rcK*3;I5*6r3c=+~ct%7Go$QPxW92y41E#yZ3*$LxNs zEDvJ<_L!fUyUahC&zO&xcbeCmm!NNdf_a=-Ypykyn6u3Z<_NPNdiTXMs`Gw!YqYHU{t_Z%u`qq=^QD-xBx@ntADP)r@yX0r$46Ot>1__56{t0 zLcU;&ex$w>GY?GEN9g_ZZkX}15HlbCi17g*(l;^p!0+k(7$I;qy#P6bOye#%%ZgMu zAyGW0&Z6HWaG%MtA`K2SY%430;5La;mJu0nEYuB_5&3T*9#dyT_M6zzdX^EXZ`5EH z>}45|_y(5jVi}S42DX2PWkk{&*r7ekh?KWj+JR+6!W;MwmWp&Yu-y+VBa+>~T_r3d zQr*Bd@3V|ZbOZ5RI3v>BK-3^*M3NiW>LZpBDQ;j2*+LC2NN*maufMrBN8;FX6j7Vn#QBjZ) z$!vl6@*2=P zNJazOm9vaUMGGut8Igzvmf{i+X=q^a9+nYFXdtT8G9m>HENaU#A^{C-gAGLbSuDln zB9hNQRGnl*>KWLwHOq*^Gq5>Mwn#ezTi|UX=`5DEU>T8e28ydeB%B4}auMlfU=y4w zk!%LWaAAm4GZ1BG8IfoPdRQvb%wnm>G9t+gAY+Nww2Dyy6m>|zTWRUN4Az>K-pkr|W@+iYtfUP~OYdbUu1QPp zWw=+;(tBB(4lFIbm$h!o($afbix^8w?`63D)6#nxJTfi4m!aw^Exnha>MAY0m!aw^ zExnh)Bh%7*89XvAy_b2|?>hP2a4V#x_c9cwq^0*V6sDx5_cFLyT6!;w4q<8Oy^Kd$ zT6!=0mb0|GECl-mfnj3O_rA4%W$)#rS~$bv$XVHR!>=4 zdapI>ewLQrYt5L&(!zTQvDSUX($ajbwU@ATgZUC`^;(ve?u!I3OH21f&XA?0`yyq? z($amAF=T1!zDO9dv~*wVl1i4A?rSYw$I{Y$twqAu$dg)A-I*E)PYOH22)aGR#3`&!p7U}@>T*1V-GE!`IhLzb5AiyR?KOZT;=W2tms zYwC2CmhNj!o66GCeJ$LZY3aV!WGt2LYfYKV($ak`)Ptv``&tvRRJyN)I`Fh~Ukioc zY3aTe?#;AxUkmqUTDq@=TJW@VUu(=*mX_{ojlpS=?rYsYhNY$ZT8GVJY3aUJ*o-Ph{)B}+^9wNPf4mhNkbn@qZ|C2lh5zQ`7`v~XWs z1PESfX}=bNS6bSyRf@Y%+OLJ8>U4ws5=-2A(tj-!v!$i~A~nd;(tj<~KBlGrTBvJF zOaHY{`|5|mx2g1B3pH?Q>Ax1rrqj}YF+UnhOaHYHrqa@XEpZo0 z|Fy(jDE-$GccJuOOWcLhe=TtrO8>RQT`2w65_h5WUlYGz($as;@)0a8{nzZ&-A)Vt zrI;o8gm3J$Fkr#C>v&o?Fj935<51GVgM~o0&rS;$7F%Vom9Sy40=YgrEsR*KIQ}|2 z-QdIxnLd`5R*c-9oocXRY9i;yQqqgf?K@dYda-%Tc9xP}Y#x0KOGz&_w;at<(u>W_ zTUbhZF*1BCCB4{OzmcV+7n_UMvy}8=vvM&@NiR05Dp^W;vAMj8rKA^|%a*g0^kNeZ zl9FC*BBZCJ7n@6#vXt~<6Cph%z1T!ZPp!u{CB$5WPm^A3!c0=qi_HZKSxS1biE$z+ z>BY$Cv6S>;^SXIwQqqjgLx!@HG-Gq{AuJ`$*u-Fvlr&=#qd`*AjLklMSxTC* z*=Hb2Ni#Mv&?6!qc0m!WG-I>um4@g45mPg+GcGdD zFfy2LeuJ^hm}^WlhNHT?v(eh{3>x_s{lm8-FJZ*^y{Ij}EOK__gvidwCiD&uk4%k> zjtoF$d2yu4uln@Ar&sDV#hd{@xL>&M|9|%M|H$Z4j4C*Ic7SjAM-4OoKhE#QxPnWN zGsy8Bd?V%un9nEkk(ev6D{q60LB!r;e`fEpU&9Om58AicS7E-uQ|tut1#2%%&;*385F{of)} z@V505<_WkLBMdIH&bCglc3PW|Cpa8`4`8%405b&?V~l}`zXI?NWC>m~|6o37-i8qd z=bNXP33Cf_1dGj?=2-I(j4x=9xdOPc9~lDtMFrzI<5A1S^KlEMtYx*BB9^f|p zD*b$n_D`TkzgAzY&(z0aG(ef&9)HPz)BWhre?Z@$&*5(w+=&W+3+O5Ic)E$MpmQ}X z+PGVv=l_rv2r`1sr0M!=0pr&?-K!W66u(#8wwUfw+@=HlPI2qD^jpO(Vsy7+>EDS^SEp|4_@l82wDK>(NgYBRBqc#nB=3 zBgH&QKMc8^eyo^r`o3a3P<&4@a^`X17n(RaP56^66iKZSX6fz7eo9X|nz&#SDLX#IJ`qYu@e3;7ZKMa82=>(444IZA&<@rW_{9~2MUr9Z8B$S(a! z#e;Y0zgIkHi2hi}gY_pA4;-XF8u9@Bam53M=}(0`P=7>m-vRo=iu?4{A5vV=M}JUp zSvUOw#l6b(`xW=>rQfHxM^F7;#oc@8_bBdGq2H~zc!Ykt;x-@ZcPK7uqyH}CBK1Qi$S)!k*xOpr6tdLvkzfoLRs-GKjbNvj( z1%>+Qienx1lN5X3>8C1oqxy-8agI(1IjWzmIO^%AgzV^bigE68itU)5R&1GiN-@q| zGUR=FMzLY*amDykPsb_7x!W0(AR$S9d{BZEt=ErLTkJFSgyQeGo>lxsfqq)RNR;c_ z6u0l7Zw>h!{TRjVe$cllF4?7TS6tjquTk9QeZ5w3QL(;JajTE?qZGF+($_0)-b`Po z7$4R1dRL#R z7=AfJagTTPX^Ok|)Tf8sL!YX+TX%hm;x66v$%;F5(tjPM)yF8ty?&VD;ywDIirW_JqZPN=qmNWv)K(uAavObwV)*oM#VuRw!xXpJqYqIG zZyp+Q3;htqxY`DX+*}`|xJgrepyF5)eSl)GiQZ4K8`JxT?CE_Kb64-9*e=z3hs^YH z#mv^r6q`)%r5IOU55@58?uv2WH%!HkE6=97oVMSlcD>$JZLx>z6^h?&ua8swVue0C zV5H~uE{c~{>YWuYnWuMBym*P;QSqY1dI!aaFVfp7UU0Zxs(Ahay(Hv$dV9t5mg;Ro zp05`xo-fly-4x&*?Mcm)28b!6;GX}w@^IgTfLd$N%!f6iYHFen<$|uD@KXoe#Iy;{84d-_VfqE?K{(b;`#p|n*90C z{~z)0^se_V@y^1$f5)MEe~q^oa{-L^hG9rTRqfBoI=Zi(B}wK2c{*Um@ITd3jxy>q{FE9M3Gt#g`_c8+nXG27n) zRPi6`403uo?J+liqQ8q?8@(v{8_W-IY{N`{3!^ho$v*@${dJ1A zj`~p@bN+wI-{CLuC(xI_o?nDY{uDo&uMOr17{q(<65b5+{@2^z*q_+%*ss`6qc49u z<_WmaKEuvpzQ3bT&3^>`V%`|^=F9Dlm@B}ub+!lf{JYqn*fZ=A^yjZaH763y{|5w(B)|=Lg=+obaztwl8b-s0~l|ohj25Y%B-;D9Q7vL51Y4q!FH?J`-G|w=zsO;Zlt}+**XFtvyYF3z?@izfu zsO{g2c>v!tUPs^lG2?FIM&mN$9OER6?bu=*X)MKDfD?@oMn9uF{?0>F1LI&L-$Xt} zcIV~DQ}|mCw?(dwTo5@ulEL_njgczkccx)Zz`^({g&iU-BQC~(ey4wmu>!B@f7Bnw zynxr~7vnD;*6BO-8htfJ3(VBV=|k~%2Rh@gTo&ji{Q=_zKA?ZWUp{<--b-)B{D9{n zUyy9nF=ll#)n0%B?{zZOUJ(7B)yY(Q0Uol{$y9rRTfpiDHsrGkkU^=FsrCY7dh2AW zy#NEB>tw3E00Y+RWU9Tua#@{BwHF}&TPIWP1$#}aPNdpxQb1Xq%(UZxS)I(Z7vPCy zT`%!rb^&%J6Ya77-?2KGXpi;%nAOQddki_bI+mqjM0*Sm z;_76gJ%(olbu!T&!!v?9nP`vU89|*)w8!v_piU;*V;K8hCll>4R6f+nM0>1Vdsf$? zp><;=?O2^mw8x4{Se;C?$66J!I+62j zE&-WbM|}&+H6+(b3=acxGPfSX)sSn*t=qABmXoP=Q|o^8Tb7fl zb-!W;%gNNb-~LOMJG$XxeKd4Vrq+Fw59DNO-7hX>Ihk7bn-{a3Os)GZnzNitt^0WF zl9Q=*zX_Jg)Vg2Tgym#v-N&<*oJ_6zc=(@_sdXO@SaLG8?z=cLnOgVJq&b;d_x%c% zlc{wdt(%jnbsw#pYe=mVAFZ2{nRTE0EGIMTK3X>?GwVKDHzzaezG+)Iky*EiPgzbT z)_s%&%M5xhQzw<;{#+~-5a)to8@F) z-Rpw`mw9y$`S0AkhSv3v|IW$0x`%r$C-dqa?zNoEt9w1mSWf2EJxnfC-dqap0VX*Ufshpw%kN{IIVFm zCrJFTHOq~c*lHNdjg#2obCw$`u_?~*7>Ow4$Q>r}bDYaVCHlDFMoTPc#d4!0x;QB# zC5qEJLZUdA!zIE(bHgNlhqE?RA||-WN$2(K0+y4`>%mQP(s{i-jOC>BdIqy{!g+1t z;j~KQ^-zJ8lg8_bbJ<|LHXh>Tr0=?AAF-VDT^F;$*t%Sq>TMe|GNb>VM0>AWudEhnAVZP$tAr1QERum|b9F8nPgo!1q& zopfHe7#m3Eby0qhlg{g6u9%#3UbhYIOX<8WruoQ8=XFuTm6Oivw#0QRo!4#t4a-UA zb@4bfC!N>DgdRERylx>-IpCr2PCBnEPM~yN{Ayx3>AbEufzo+haRR0Dx+vAj3FozmixVh~*A@4rG+z8|XqJ=4 z>xy&PV7#_lk3SHMH&OgSmD@k!Ibpn%I@%f9?RHLBuUK(vg6D+yiWR3}7QdWuUm;G_ zPP22uf5lcOV=H06V#P_Aw=X9wSPHyFn6MCH-oBjhVIgvu(Jw2USO_tRUsiar5NWYS zxUmo^OxBkbhAf1bp)V^eS&A&r3R4#1cr9sX8*G_67&XDN(wC8Dva=1oOr4$D87wP} z+1YU_%SvN*w(nqBY0S>n?JO&e**SVE%SvN*@K`k~jTs3nmX*fr9JP~Wr7=6z7qF}} zW(QOAWu-AYM{ZzQY0S>rBUx4&v$JL`%SvNLW{hQ}F*_@cVp(a-&a!J+RvNQ|hpky@ z%+BIUmX*fr;IV2}8nc6Esaa{v&b13!Rv0rO&b$>YE1lVyav#e|XLjZQr87G-=df&p zGZSalOqP|_?980SveKFz6lP?lH9IKG$VzK=rcPm5Y0VC%;mb;Ec2IYbmDY@OHOoqC z#*+q?mDcP`n8dQunjO?#WTiDbW5=_sv}WhNu`DaC*%|!^%Svl@#>`_`Y0b`|qghs3 zvxBF#S!vAe0;(wd#YSSqdA z83L5n?BKa>zMu zr8PTc-B?yyvje-#N^5pd@0gX=>>yfYr8PTn&aAX%r&|Tf3TwuREh=JJ>CMir?kp?4 z*=h43%Qkp3HfY1L(wv>*5iBds*+B(jR+_Ve5S5kY?6jJ6lXiID$ z%{eNXM4EFHGYe*=IY%*NU{;!Q6hDBo(ww8-cPuN+8Kulok7cDhV_tfeb>z}mN0ycD zj4F1PZE$B270oB?S-cI+C)^n*nooE$5WzkxtQlzTV_9L$Ks&~=!j^%S$s@Y9Uq6mS zpT|u3cSWy{UX0#z7WMPh(dE&3(Miz}(Z10x(V~XF^LPC3{GEfI@n3)NU;Fd!##n$0 zyi>e5G65?w|Nlhf0V=#sUMtV@boV>V3iyuuvip1YKKEv10nTwxbdPgu+*R%om>2L+ zcc9zDEy4VK|4)1G9VJzQuv=e2K8ilQH?S}N*VxhTd3iDV@~?O9m8W4({`qo+oG6FO4(Q2GI4uZo zzQ=BUZ(~>fC!8Cd%dvODvshO@);Skz?a#*U{705o+_x^TxHqfq&071f%v$^MP77wA zg+s+WF;z?uLowUlD0|glYO_n9{ciNYOk`F+Y3S`g>DV~WOu<% zego`1>`|es(Q}`{>5K1$UI{%DdJvuYmC+d1Py817Ch}qAjmYznN3o~k^~g>NSUs^K za#&=3WO`&`WO$@cqyv(ZFxF0dAO0l#HqLbDU)i~`9_u=SIHU3Nigznsu6P==`Gc_! z;69;ULqkG6a27z@Pz`$de-C~e{0MvfzYu&ZcvtWS)c%XX(}OF6%Yp|6X9TC~tvk)@ z|HS|EC*VbKRo}$yN10{RzLVLJ_8mUA*mp61UtvGU9IdkNV-EjfKftWR_WjJRw(n(D zu6+;lFUr2V;^}{MZSdoj-`kHcS5(>$Gym$?w|fPps-S%vKOA!GTbZ%%=q+BMsj9`k z*?SntiG36EfY0n3nfnj0H!%0xlKF!eCAXedmVE!WuM1fm$c7i{;bYEhdD9GZf36i)-Ewu*V;wqs%pEy z{B4!Z<;7MNKiWBdJX&FAnOi07409xEr&D?LQy^6X0HhU#AeiJL0+qSchXKrk3AIBVTw2x&@#O-64;|cp{=IXe86mzWF zK9adQW*@=as@mq#B!U-vIX@o#(O$-kPy8_E%6|4zW_6cFdNAagJXfNc4ZO2}~j8FVPW_;pw5V#wxY_SjU9!3yj&to3)iM>B_ z-y!x~=6-$cIn4e0+50i~>u=9y?%U6v#f*D*CNu8ceVKdqwr4Q+=xI-9?%u>oJ9e?T9Eo7Yp3IN8X>0Gr+}Ot6lexap-h=s*dV3Oca-_XGbK)C& zB6Cf`-i^7s#-6}jm9)n(SN>%0${ek<$1>x$K86{;>Cwz^=~2vXl|7spEt_0yuu_6+xSh2zP@OE%rdZ;%8+KW&TOqBbmRcw#R#n$i^PP+^e_U zpSedbyB{-tD}9-}eQI-I5)qEwhaZQd_F~3ws3$WVwFfgCwL5c%PwlSEaM3Qz?K;`r zd~R=dW^Vk{?!?@>(eB9H(9Uk}b8EYU$C!bKzF|K6h|t%}haDdJih1c_p)Z*aT^jm= z`JkghpD{07ANrJe!NSld%m*w8eawt9=vL9Eo^?>@@67u?7{poK z&CmJV#(tH#O;7t7pWE8cGUHDE3v;T`ewsPiXg|e_QT0jYM9O~L=Q{fdX57^qnPWBf zW6V{*+7Ee*5YTSNT=%oxhB^L&-N;-Uw_7vEYV8y=#)o?5s#?3j=W092Tv25wm@!hv zeU937%#k{~hS`nS)y!~;DxX{I7_+17N@kdUlo{q9VTNmzPnw5oM1wY+O78}vEw<(> z;2N!%e~8+(%%3Lhwh`HYk)Z8(JDHR-L0kAQE1YUeU*t}; zZU4G#pkN1gQaL;1D};o$DR0+jv(d-;8WZvL0_(?}U^!)bpP=>mEHR_Vj_BJ}f5*5i;f^wjNiT!(c4 z`}zIzT>v*BZ&;_Y*w^oPg)>rc`rjUEv>N!|+y!s~_V-)j%yp)qzkm3DvI}4VR(R|w z#)`qBhiHq`0n5GrMO^@?P^*v#{u=xSxxt%QGw?+4zTnNlt8hwS7GJwSKxHU#kKaF0 zWhilvj{{QT9v}aR%247S-*vpoP~sj(%A29YJ^sizm7&Bvj^#NSO5EdPr>YDk?(tEt zsSG9V@gdKt%(8Ow6@PAs%247S$C{iBCGPP-FQ^P9?s2Ti$xz}R$C{iBCGPPT2B{1s z?(qRbRfZDx_~1<{Ly3EQ;9!-Z#66A$J{d~fJ+~a5k$WY=QNBuHGiF>@=`zk|;dmKq4d1H_ zCGPPyJyeDg_juw9mD!6vUlgk|l(@&gh^q`G?(v$hRE84wI2IgcC~=Qt!C{6H_xM*e zDnp5TygI2el(@%hzg8Ja+~cUFWhilvqn4JT#6A9XwaQT99fC~%LXmX;|8?rywAWhig2O?;^`l(*NKkBRd3S_J1A%G+zP=|hI{ z_Szb}jPmx{>fcp{^7dNukEFc47RBETrY9|>=+iOrI$xzx}GhnmIP}*KI2=^tW?KN07m!Y)1 z1_hH0rR_B+m}Dqzui4yBWhia0>5F@f()OC(uc!>A?KQ99uA;QPrdMB;p|riG>lT%v zw7mwk=nSRpHCu3}P}*M83EyZ++iSYwPNB5D1_kI0rR_B+KxZgzuQ7KOrR_ECaV@3o zH4V4}DQ&N5i|;w5?KNl+$b`zbM-3VTGC@KVpfeWX*48Q$AWXHW^ht!ZU#j$pgmtwl zy_OK`anmOd;!BiX<6Rq9>D7ez5~WuW)}&N=C1Le8m0m%JnoOF!w+8ER)8xH1XctM7 z_tsR_s5E(RP4qXFChx65H&vRvw#7!3cLe3Z|s`=ZzhO zN6C3(%h#ziId5#)a+N0Mjjdaz(&W6crAMhWId2Rr@YCeHF|5E(lk>(dI#{L2d1Fgf zs5CimY|))6P0ky`Qra{*Zw%GlG&yf<{sNUI=Z($Xpwi^Lu{m>9nw&R=RrP6d-WXQO zrpb9@SSgz(=Z$TcrPAcQv6=H#nw&Q_W2Q=z^TwvlP-$}B7^=Hza^4uKyJ>RX81}(P zlk>*z+()I!d1I58s5CimY>!t}nw&R=RrP6d-Wb-;r^$I^6DF!OId5$2Gb&Ba8$)e2 zP0kw|Hb$k%d1KEEQ)zPE*w6_oP0kw|GE}9>d1Ek&v~ga`ioqz-WW6zrxoNWASl-f1DoySi!`PT6_l;q#eVW`ihVGLzxo-^JCuwruSR|&>o9h zP3{{LQl^djx>gK#F_~`+rP(x@Zw#f`G?{PA+|Xt7bz`_4$$P5@Zc%CS-s*n%U6S`! z_wJ|Cc;jeP2O9LU7MQ7d#kZOQxkb__2=kwB=4<`qt}wWx4Py#)kNM~ja?6#$a|~t zr%4leZ#7)DiM+QOF55)jTaC}WiM+QO$Ne;s_f}WLRTFt{b>vsoMBZDCvAl`Aw;C?n zMBZBsmu({Ntu_s?$R%J0Lgo+u;)w@d2iLo(W;5Ow`$Z#)kNM~g*|7Q z$a|}X;ZgG5s^LKL-l}24RTFt{75+wOBJZujP2NP_TQy{yY9jBg8aPBXk@r^h?4g>- zd#ie(Etb3&b8yu}-iyRbHIersXPg-Scl&kwIqVO2hkc!Wk)6lca>v_Cac2B9><%{!{o(EHxUK(9cfd{msr~<- zt$3v3j*9ClE<)eHsTIdpEX4|fX%)Lw46EqGyc(9(46zhVuZ= z46P0w9$FZhi5>k$h5DnXzaFOn27^BaKM%eed^z|uI{I(HTz(zS0z45r04xd4p^VKW zO#l69+n?|FulxkOR1kxk{ReZ+clPhhh{k_oMx^~KGm6i@Ft@6*e`c<1Wphf2fz9S* z5+j?<$s~q0o0Cb5Z8j&97~Je_{62`czh#!n<}wZjHJeK}7|U!f*I+QSKj-TaZ-3?$ zpR0rRmtI>!b&JhK9E_4S7lSYo+nl9hw6}NgyKLz$q{kQvZ7%v?EVQ}kgTc_|q7Q~Z z`$N8dzTi-6}Nhf zC`nw(Jam}2gn5^t;$r3@yNHXJ2MrMyFb^Cg&SxI5SzO56e}GuW+^@elkGbzaaSn6u zS41;&ufC$l+@qJsGk5JIGCpq+1?DbYMVh&D7tzGrsfWn=+*zE>+`f}Ii@9xsID@%O zTX8ybV;gZAbL&QNDsw}7aVB%BMV!oBS1V3rPSlCD%=q@4z>IIt8lS%uCo$Kg#46_M zZDJ*JRkc{b+^R|(&s2CiX8JR zy+w(6b3bve$H<+;LCmXGi$y*+iN(w-SBZtpD_X>X%*P%h4q!g!STWD%i^P29qmB{# zGcR8!<}xo^F6J;VJxa`GK4htw#eDD~VkYyF6=DYSqC3TO=7o#IH0A{h#Z>0`3&a%W zxf{ej%yZ_7y_xr$BPKJ?-cRhsJZrYtlX>QRF^PG`OtCxjv>9R|^VDf#H|8nmiDlkr ziWtv4d5IXuyvM6zEc5Prh%w9)cNe3XCrlKhn8!XNMlz2XD@HI68zY7@4@D;~^N^up z7iPH05N5c^VCDe>#USRs1H?e)K7GXi=H7iof977jML*`AlSLoqZeNMs%w4;QUd)}l zik{3JI*T66Z99mr%y5S;%&oVI&dd$1MJMJ|8_|I|`Kjp6T=%(X#|(F9%N$RM_C6;> z8|Iq0Xw4k!DN@X>eh~G{_$wmGTu~|Nn4=XU!5oQ+T4pyQYM7-fV$4E{YGzxAR?HzB zFvpC$z1**W0(mSbs=T{B)*_;O#Sc*tXU1O+4a}d%MI-a4NzsjYYlG;>{Aw@Jm-(4t zVi@zC`-okcH_Q@yFkf`A*q3?TGO-^svY!PWBij-Y=6(Z3n7Ma9p_zO17A|x59zrp9 zZ4nN0=T5?A?$lX?eEwVrW{f96=0;?R%&A5ZV6IEqEvEiIIC$)Twf=u)#kz_NPVYOu z;;@PXEA~a-|LBT=72PTtD`MEe?+={c_g}C7pAb0;r})i{Oo>d03_}-t$4D|#5ebEV z4u6Gn{N4z^5Z)NR2P+t^2%i^DhffM08$J{#`OOGV3Xj4XhX20$|2b}xdm_&CJH(xb zJ^d!SBe2q-vzu~T;Z(n0)z|7n%;`5_SHF7|)&Dcfm+pym(C9jr|=jN50b} zP87$8L&_@;#*1B0{cjXi$aj9i{*Le1o9!p;d$8)@61#wO=Q#Ti?ESwtPInxLbqDoW z`yU8>kBs2W&pON zQXlPrq?59%Tt(MkGMBaVR}6lUgs%HuSdq4r!c)9kz1a^bR1Ht@)V}mqh6b* zFufibZ=S;RdL5Q|6Q;XXy_R{CrDKoy7L}(gJ-Pn|H*d1^pp^{V(xUSwOb-T=fg4*E z>AZ>4jo1*lQRgF`SRdG+^CnX_&s-N+uk$8VH{zPWbvkcybtA40T%+?QSvTUUz|}f$ zvUMY_2wbJ}CS5n;^1u~3Z}N4BmPLWfb>1ZG=9x=dR_naU*p0ZjhS)p?#c{jo=EX|qh-MnT6mXGF4`fi?S3gmUpCSpV1jsam04t(D@XCc7mP)|lqXVZ4<*4v$hA zpFHL`m7_F1dGs+VM`?Vrc(lq<8lOD!Ta}|UK6&^}Dz}jSYs>amIZES`OP8q}rSZun zOI422_~gM$RF2a4)DmRP}3rllD z3DGi<+lBD?Au2b7aL^c)8%(%qkjf1r957Dh1`_uBP~`>?_W3~N`V*oOn(If1dP%M? zA?hW$K7`$Ss$6fvuH99x7a?poNA{g;+ePKd_H8BGw^ccE?_|gJDo5^}g!Sggy_4-a zs2sU>66KIw*}bhKI*M{+-pMxoRF2F$`C%KCBlAwS9-wk$-pSNwDo5s>#DdivnRgPs zLpd_<`epTz{%H|zRwhEl7a^&90SVHBO6ADClQ>BuNA8`3d*{f#lRBbu6lHI=94y74a6XqGe!->SvEOi(?Bc@&KZ*iVrg*BSTxXlyvCq`jV&r` z>=}rq!C7O@z{J-oYpfZF@^jW0GY~cBtg&SvN>^E9%D~zhl{J=ZE~r&mW5_@Zo>^nZ zz^`K}Ys?tfYP-rBD+Xfl%o-yGq7f`>Y#4}Ur>rqyU`14AjRl)WD^%7vFc80#Y}tRU zy6q8_CHt*&e^pts-@0(M%98!o;rEjz`>p#`sVv!VoeZli*>9b6RF>?w&XF=}?ANvG zf+|byTZam2mfW`v71S)bZyhSA*|Ph(SV5g7^G%F?N@dA>69YG^ESYb@d>~}L3G;!F z`6eceR#`IN#P|s+OXizE_hXjKH!*fsl_m2{pm`!o=9?IUmy!7p1_nQvkc zUPk7dKqp3)%r}8fj4YXN0^M6#GT#Kcx3Xlu33P8|$$S&t-%?pJ-$d{3Dof^@fGcIm zd=q_#t1Ovs0tS^O^G#qqe3r~N(E~3d^G%>DAWP<(z)ne7GT#ImII?8E2{>4m%s25? z7nLRRO|;pnvShxA&V5yu%s0`ojmnbwCSYb+GT#KuEKBB_z%7;~^G%?roF(&3;BL#3 z`6louWtPl0fj=p;WWEXXePqde6RFNBOXizE3rLpCHW9vH$MFql_l?uV`q&l zd2jrdX(~(JivoelkoU%?qOX#?H@^3yDns5IpLCzfkoTgnpfcpWah!gVA@7ajFPRK^ zZ+zmODns5I-~E1-A@7Y(+`U}?5B(9qE^$|i^DsYLD~=M2#eQNky2A&FZlX2jhr<2^ z_5Tm-*Kj`E!}jg=wf2Q}&OQY*#6#_QI2~>R_J`|bx3g<)_n)Z$|E1!girfE(*8k19 zfst@P|KFSd@V~47AMGCG&UAN2R?rta2G+R|H=uq{pW?K@&FV>YpSoFHg_Qznb)q_2 zEm3pS-Z(38u=k&S%*j*aaq>_(Pfo=-fkS04>=all zUFQ#Ho3qt<)7gYm0`GR#W1qm1bB43ZS?(-w_Qe^2Be6c9lan+Ha>RD=nRo{$1U@D1 z5B(9^7TSuL{ie`H?CG~YbZMv*IwQ0yv^=x`=lM+vjl_M7Jd9p!8O6{Rg$^QhOv z*UTeEi7%LkjS!zPqfqxL^N{DnSImPqiLK0oUJ#!%4;(B$VjeJ5eC+c;@gZ}+mmQ9X z;qeYf#4vfMim&O@*WtJr3oOL@{CKb4;yvb`1H=bD_Y&_i_ZTbQV($8Zc$*nf{2R;& zTwiDI*h##`-2OfBCUd*@#VgEhJBn9*ZYN%5Zqq~jmAT=2@gj45gLsxXSug&=ocKa) zVnzY)1&=Y0cKBz&MB3s17IGrVp95yq4*xuuRy+JNU^4A&=J%O#q{BZC=FkrRw=i9H zp5tpK@9pr&V$Um40j%2919JC`xn ze&t-s97{MCF;~T$3%wPGX145H%ulqcaxP$wwsO`nN21Po%sS$n%j|0B9A@P@&CF6c zC1yuDMP}hR1!h|~d9R|Iw4Iz+<4LwSS-#@iR?hj%U)4I7Fn^YGE@%F*jdKn22W_3} zm^Te_?qPm@h;twFBjcTo%n$A6Ji&a^Oowk%L{iR6{CM$bXREhvvz!e&-}wqrqw|xm z5LP<8-m@H@O0L&nhVNv&_rtv2X<}~Q(MkKJ16*D>#Smq)i^7etCP;L z%&oRN$1_*9a*kuJh&o3zM=PCUe6DbgV#c@YNak?0vz*!e)j6CQ->zlMGVCn%*>MhK z#y9N{X4`R=Fo$esF>^5F9OQlL>Vi(Wr2z46U5m4bulQ9thcR!DI7cvl9dlMNe^T$9 z#QgU*&gmXwdhZ;-Jbr>Rk9pVe&i>3}cXj46j~VOC@p-hvl|@YQo!R{O=%<{8%%es- zvzSMYa%M7*80qZGJiNu3!aQ`KGo5*tq0Ths!Miw9nFkGa_VIb3vp4g=LC$2KH#-ZM z2Ml!fV(ve{*^{|ne`gQozQdgf%)Pri6PaPIyD`ID$20fn>FnxrcV`@P_a4qzpWkwJ zXYSVB8N=MQn=_gjW;}|yb6;mTbH_H$FlHF@P-d9%F3h;ohWOmZ8O)5kZjjGgoe|89 zZJdG3ts9*I%nhxb{>-V)PCw@4k4_)vx`fkM_}aW|%zaW^KJaW~d6<8DlNcVi;x)O%wf(%K z-rUcb#Qf9nD>5E>|ow&Z}A)Ro_mR3nJ3*R{$Sqye(^Kʮ?KJPAmVxI7j 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 E08A$W`v3p{ literal 0 HcmV?d00001 diff --git a/coverage.xml b/coverage.xml new file mode 100644 index 0000000..1d8ebaa --- /dev/null +++ b/coverage.xml @@ -0,0 +1,2322 @@ + + + + + + + /home/kenny/development/github.com/httpdss/struct + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/struct_module/content_fetcher.py b/struct_module/content_fetcher.py index 8c7686b..bd75658 100644 --- a/struct_module/content_fetcher.py +++ b/struct_module/content_fetcher.py @@ -84,41 +84,41 @@ def _fetch_http_url(self, url): return response.text - def _fetch_github_file(self, github_url): + def _fetch_github_file(self, github_path): """ Fetch a file from a GitHub repository using HTTPS. - Expected format: github://owner/repo/branch/file_path + Dispatcher passes: owner/repo/branch/file_path """ - self.logger.debug(f"Fetching content from GitHub: {github_url}") - match = re.match(r"github://([^/]+)/([^/]+)/([^/]+)/(.+)", github_url) + self.logger.debug(f"Fetching content from GitHub: {github_path}") + match = re.match(r"([^/]+)/([^/]+)/([^/]+)/(.+)", github_path) if not match: - raise ValueError("Invalid GitHub URL format. Expected github://owner/repo/branch/file_path") + raise ValueError("Invalid GitHub path. Expected owner/repo/branch/file_path") owner, repo, branch, file_path = match.groups() return self._clone_or_fetch_github(owner, repo, branch, file_path, https=True) - def _fetch_github_https_file(self, github_url): + def _fetch_github_https_file(self, github_path): """ Fetch a file from a GitHub repository using HTTPS. - Expected format: githubhttps://owner/repo/branch/file_path + Dispatcher passes: owner/repo/branch/file_path """ - self.logger.debug(f"Fetching content from GitHub (HTTPS): {github_url}") - match = re.match(r"githubhttps://([^/]+)/([^/]+)/([^/]+)/(.+)", github_url) + self.logger.debug(f"Fetching content from GitHub (HTTPS): {github_path}") + match = re.match(r"([^/]+)/([^/]+)/([^/]+)/(.+)", github_path) if not match: - raise ValueError("Invalid GitHub URL format. Expected githubhttps://owner/repo/branch/file_path") + raise ValueError("Invalid GitHub path. Expected owner/repo/branch/file_path") owner, repo, branch, file_path = match.groups() return self._clone_or_fetch_github(owner, repo, branch, file_path, https=True) - def _fetch_github_ssh_file(self, github_url): + def _fetch_github_ssh_file(self, github_path): """ Fetch a file from a GitHub repository using SSH. - Expected format: githubssh://owner/repo/branch/file_path + Dispatcher passes: owner/repo/branch/file_path """ - self.logger.debug(f"Fetching content from GitHub (SSH): {github_url}") - match = re.match(r"githubssh://([^/]+)/([^/]+)/([^/]+)/(.+)", github_url) + self.logger.debug(f"Fetching content from GitHub (SSH): {github_path}") + match = re.match(r"([^/]+)/([^/]+)/([^/]+)/(.+)", github_path) if not match: - raise ValueError("Invalid GitHub URL format. Expected githubssh://owner/repo/branch/file_path") + raise ValueError("Invalid GitHub path. Expected owner/repo/branch/file_path") owner, repo, branch, file_path = match.groups() return self._clone_or_fetch_github(owner, repo, branch, file_path, https=False) @@ -143,18 +143,18 @@ def _clone_or_fetch_github(self, owner, repo, branch, file_path, https=True): with file_full_path.open('r') as file: return file.read() - def _fetch_s3_file(self, s3_url): + def _fetch_s3_file(self, s3_path): """ Fetch a file from an S3 bucket. - Expected format: s3://bucket_name/key + Dispatcher passes: bucket_name/key """ if not boto3_available: raise ImportError("boto3 is not installed. Please install it to use S3 fetching.") - self.logger.debug(f"Fetching content from S3: {s3_url}") - match = re.match(r"s3://([^/]+)/(.+)", s3_url) + self.logger.debug(f"Fetching content from S3: {s3_path}") + match = re.match(r"([^/]+)/(.+)", s3_path) if not match: - raise ValueError("Invalid S3 URL format. Expected s3://bucket_name/key") + raise ValueError("Invalid S3 path. Expected bucket_name/key") bucket_name, key = match.groups() local_file_path = self.cache_dir / Path(key).name @@ -176,18 +176,18 @@ def _fetch_s3_file(self, s3_url): with local_file_path.open('r') as file: return file.read() - def _fetch_gcs_file(self, gcs_url): + def _fetch_gcs_file(self, gcs_path): """ Fetch a file from Google Cloud Storage. - Expected format: gs://bucket_name/key + Dispatcher passes: bucket_name/key """ if not gcs_available: raise ImportError("google-cloud-storage is not installed. Please install it to use GCS fetching.") - self.logger.debug(f"Fetching content from GCS: {gcs_url}") - match = re.match(r"gs://([^/]+)/(.+)", gcs_url) + self.logger.debug(f"Fetching content from GCS: {gcs_path}") + match = re.match(r"([^/]+)/(.+)", gcs_path) if not match: - raise ValueError("Invalid GCS URL format. Expected gs://bucket_name/key") + raise ValueError("Invalid GCS path. Expected bucket_name/key") bucket_name, key = match.groups() local_file_path = self.cache_dir / Path(key).name diff --git a/tests/test_content_fetcher_more.py b/tests/test_content_fetcher_more.py new file mode 100644 index 0000000..0a15b7b --- /dev/null +++ b/tests/test_content_fetcher_more.py @@ -0,0 +1,160 @@ +import io +import os +import stat +import subprocess +from pathlib import Path + +import pytest + +from struct_module.content_fetcher import ContentFetcher + + +def test_fetch_local_file(tmp_path): + p = tmp_path / "file.txt" + p.write_text("hello") + cf = ContentFetcher(cache_dir=tmp_path / "cache") + assert cf.fetch_content(f"file://{p}") == "hello" + + +def test_fetch_http_url_caches(monkeypatch, tmp_path): + url = "https://example.com/data.txt" + + class Resp: + text = "DATA" + def raise_for_status(self): + return None + + def fake_get(u): + assert u == url + return Resp() + + cf = ContentFetcher(cache_dir=tmp_path / "cache") + monkeypatch.setattr("struct_module.content_fetcher.requests.get", fake_get) + + # First call populates cache + assert cf.fetch_content(url) == "DATA" + + # Second call should read from cache and not invoke requests.get + def boom(u): + raise AssertionError("should not be called due to cache hit") + monkeypatch.setattr("struct_module.content_fetcher.requests.get", boom) + assert cf.fetch_content(url) == "DATA" + + +def test_fetch_github_https_and_pull(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + repo_dir = tmp_path / "cache" / "owner_repo_main" + file_rel = "path/to/file.txt" + file_full = repo_dir / file_rel + + # Simulate repo already cloned -> should call pull + repo_dir.mkdir(parents=True, exist_ok=True) + file_full.parent.mkdir(parents=True, exist_ok=True) + file_full.write_text("GDATA") + + calls = {"pull": 0, "clone": 0} + + def fake_run(args, check): + if args[:2] == ["git", "clone"]: + calls["clone"] += 1 + # create the structure for clone case + repo_dir.mkdir(parents=True, exist_ok=True) + file_full.parent.mkdir(parents=True, exist_ok=True) + file_full.write_text("GDATA") + elif args[:3] == ["git", "-C", str(repo_dir)]: + calls["pull"] += 1 + else: + raise AssertionError(f"Unexpected git call: {args}") + + monkeypatch.setattr(subprocess, "run", fake_run) + + out = cf.fetch_content("githubhttps://owner/repo/main/path/to/file.txt") + assert out == "GDATA" + # Since repo existed, should have pulled + assert calls["pull"] == 1 + + +def test_fetch_github_clone_path(monkeypatch, tmp_path): + # Force fresh clone path + cf = ContentFetcher(cache_dir=tmp_path / "cache") + repo_dir = tmp_path / "cache" / "owner_repo_dev" + file_rel = "f.txt" + file_full = repo_dir / file_rel + + calls = {"clone": 0} + + def fake_run(args, check): + if args[:2] == ["git", "clone"]: + calls["clone"] += 1 + repo_dir.mkdir(parents=True, exist_ok=True) + file_full.parent.mkdir(parents=True, exist_ok=True) + file_full.write_text("X") + else: + raise AssertionError("Only clone expected") + + monkeypatch.setattr(subprocess, "run", fake_run) + + out = cf.fetch_content("github://owner/repo/dev/f.txt") + assert out == "X" + assert calls["clone"] == 1 + + +def test_fetch_github_file_not_found(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + repo_dir = tmp_path / "cache" / "owner_repo_main" + + def fake_run(args, check): + # Ensure repo exists but no file + if args[:2] == ["git", "clone"]: + repo_dir.mkdir(parents=True, exist_ok=True) + elif args[:3] == ["git", "-C", str(repo_dir)]: + return None + + monkeypatch.setattr(subprocess, "run", fake_run) + + with pytest.raises(FileNotFoundError): + cf.fetch_content("githubssh://owner/repo/main/does_not_exist.txt") + + +def test_fetch_unsupported(): + cf = ContentFetcher(cache_dir=Path("/tmp/cache")) + with pytest.raises(ValueError): + cf.fetch_content("unknown://foo") + + +def test_http_error_bubbles_and_no_cache(monkeypatch, tmp_path): + url = "https://example.com/oops" + + class Resp: + def raise_for_status(self): + raise Exception("HTTP error") + + def fake_get(u): + assert u == url + return Resp() + + cf = ContentFetcher(cache_dir=tmp_path / "cache") + monkeypatch.setattr("struct_module.content_fetcher.requests.get", fake_get) + + with pytest.raises(Exception): + cf.fetch_content(url) + + # Subsequent call uses requests again (no cache file was created) + called = {"count": 0} + def fake_get2(u): + called["count"] += 1 + return Resp() + monkeypatch.setattr("struct_module.content_fetcher.requests.get", fake_get2) + with pytest.raises(Exception): + cf.fetch_content(url) + assert called["count"] == 1 + + +def test_github_invalid_path_raises(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + with pytest.raises(ValueError): + cf.fetch_content("github://owner/repo-only") + with pytest.raises(ValueError): + cf.fetch_content("githubhttps://owner/repo-only") + with pytest.raises(ValueError): + cf.fetch_content("githubssh://owner/repo-only") diff --git a/tests/test_filters_more.py b/tests/test_filters_more.py new file mode 100644 index 0000000..bb772eb --- /dev/null +++ b/tests/test_filters_more.py @@ -0,0 +1,93 @@ +import os +import types +import pytest + +from struct_module import filters + + +def test_slugify_basic(): + assert filters.slugify("Hello World!") == "hello-world" + assert filters.slugify("Already-Slugified_123") == "already-slugified123" + + +def test_get_default_branch_success(monkeypatch): + # Build a minimal fake Github client + class FakeRepo: + default_branch = "main" + + class FakeGithub: + def __init__(self, token=None): + self.token = token + def get_repo(self, name): + assert name == "owner/repo" + return FakeRepo() + + monkeypatch.setenv("GITHUB_TOKEN", "tok") + monkeypatch.setattr(filters, "Github", FakeGithub) + + assert filters.get_default_branch("owner/repo") == "main" + + +def test_get_default_branch_error(monkeypatch): + class FakeGithub: + def get_repo(self, name): + raise Exception("boom") + + monkeypatch.delenv("GITHUB_TOKEN", raising=False) + monkeypatch.setattr(filters, "Github", FakeGithub) + filters.cache.clear() + + assert filters.get_default_branch("owner/repo") == "DEFAULT_BRANCH_ERROR" + + +def test_get_latest_release_success(monkeypatch): + class FakeRepo: + default_branch = "dev" + def get_latest_release(self): + class R: + tag_name = "v1.2.3" + return R() + + class FakeGithub: + def __init__(self, token=None): + pass + def get_repo(self, name): + return FakeRepo() + + monkeypatch.setattr(filters, "Github", FakeGithub) + monkeypatch.delenv("GITHUB_TOKEN", raising=False) + # Clear cache between tests to ensure function recomputes + filters.cache.clear() + + assert filters.get_latest_release("owner/repo") == "v1.2.3" + + +def test_get_latest_release_falls_back_to_default_branch(monkeypatch): + class FakeRepo: + default_branch = "main" + def get_latest_release(self): + raise Exception("no releases") + + class FakeGithub: + def get_repo(self, name): + return FakeRepo() + + monkeypatch.setattr(filters, "Github", FakeGithub) + filters.cache.clear() + + assert filters.get_latest_release("owner/repo") == "main" + + +def test_get_latest_release_error(monkeypatch): + class FakeRepo: + def get_latest_release(self): + raise Exception("no releases") + + class FakeGithub: + def get_repo(self, name): + raise Exception("bad repo") + + monkeypatch.setattr(filters, "Github", FakeGithub) + filters.cache.clear() + + assert filters.get_latest_release("owner/repo") == "LATEST_RELEASE_ERROR" diff --git a/tests/test_utils_more.py b/tests/test_utils_more.py new file mode 100644 index 0000000..964709d --- /dev/null +++ b/tests/test_utils_more.py @@ -0,0 +1,65 @@ +import builtins +import os +import subprocess +import textwrap +import types +import yaml +import pytest + +from struct_module import utils + + +def test_read_config_file(tmp_path): + cfg = tmp_path / "config.yaml" + cfg.write_text(textwrap.dedent( + """ + a: 1 + b: two + nested: + x: y + """ + )) + data = utils.read_config_file(str(cfg)) + assert data == {"a": 1, "b": "two", "nested": {"x": "y"}} + + +def test_merge_configs_prefers_args_and_fills_missing(): + class Args: + def __init__(self): + self.a = None + self.b = "cli" + self.c = None + args = Args() + merged = utils.merge_configs({"a": 1, "b": "file", "c": 3}, args) + assert merged["a"] == 1 # filled from file because arg is None + assert merged["b"] == "cli" # arg wins because it is not None + assert merged["c"] == 3 + + +def test_get_current_repo_https(monkeypatch): + def fake_check_output(cmd, text): + return "https://github.com/owner/repo.git\n" + monkeypatch.setattr(subprocess, "check_output", fake_check_output) + assert utils.get_current_repo() == "owner/repo" + + +def test_get_current_repo_ssh(monkeypatch): + def fake_check_output(cmd, text): + return "git@github.com:owner/repo.git\n" + monkeypatch.setattr(subprocess, "check_output", fake_check_output) + assert utils.get_current_repo() == "owner/repo" + + +def test_get_current_repo_not_github(monkeypatch): + # Current behavior: any git@host format returns owner/repo regardless of host + def fake_check_output(cmd, text): + return "git@example.com:owner/repo.git\n" + monkeypatch.setattr(subprocess, "check_output", fake_check_output) + assert utils.get_current_repo() == "owner/repo" + + +def test_get_current_repo_not_git(monkeypatch): + def raise_called(*args, **kwargs): + raise subprocess.CalledProcessError(1, ["git", "config"]) # simulate no git + monkeypatch.setattr(subprocess, "check_output", raise_called) + assert utils.get_current_repo() == "Error: Not a Git repository or no remote URL set" From 97a5f8f16e88e6a473e3fa6bd28b81bedf1874c5 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 9 Aug 2025 21:14:44 -0300 Subject: [PATCH 2/5] chore: ignore coverage artifacts and remove from repo --- .coverage | Bin 126976 -> 0 bytes .gitignore | 5 + coverage.xml | 2322 -------------------------------------------------- 3 files changed, 5 insertions(+), 2322 deletions(-) delete mode 100644 .coverage delete mode 100644 coverage.xml diff --git a/.coverage b/.coverage deleted file mode 100644 index 4e72eef94de73dc11b7a9e3d32f51c6c12deb051..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126976 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ʮ?KJPAmVxI7j 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 E08A$W`v3p{ diff --git a/.gitignore b/.gitignore index ff9930a..769fc1f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ build/* # MkDocs generated documentation site/docs/ + +# Coverage artifacts +.coverage +coverage.xml +htmlcov/ diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 1d8ebaa..0000000 --- a/coverage.xml +++ /dev/null @@ -1,2322 +0,0 @@ - - - - - - - /home/kenny/development/github.com/httpdss/struct - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 04578b4a331c30e56b7948ff112727ab971f513a Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 9 Aug 2025 21:17:18 -0300 Subject: [PATCH 3/5] tests(content_fetcher): add error-path coverage for http, invalid github paths, S3/GCS unavailability and invalid paths, and git clone/pull failures; reach ~75% module coverage --- tests/test_content_fetcher_more.py | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/test_content_fetcher_more.py b/tests/test_content_fetcher_more.py index 0a15b7b..88c9c0f 100644 --- a/tests/test_content_fetcher_more.py +++ b/tests/test_content_fetcher_more.py @@ -158,3 +158,64 @@ def test_github_invalid_path_raises(monkeypatch, tmp_path): cf.fetch_content("githubhttps://owner/repo-only") with pytest.raises(ValueError): cf.fetch_content("githubssh://owner/repo-only") + + +def test_s3_unavailable_raises_valueerror(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + # Force unavailable path; dispatcher will not include s3 and treat as unsupported + import struct_module.content_fetcher as mod + monkeypatch.setattr(mod, "boto3_available", False) + with pytest.raises(ValueError): + cf.fetch_content("s3://bucket/key.txt") + + +def test_gcs_unavailable_raises_valueerror(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + import struct_module.content_fetcher as mod + monkeypatch.setattr(mod, "gcs_available", False) + with pytest.raises(ValueError): + cf.fetch_content("gs://bucket/key.txt") + + +def test_s3_invalid_path_raises_valueerror(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + # Ensure available so it reaches regex + import struct_module.content_fetcher as mod + monkeypatch.setattr(mod, "boto3_available", True) + # Do not mock boto3 since we only test invalid pattern, which raises earlier + with pytest.raises(ValueError): + cf.fetch_content("s3://invalid-format") + + +def test_gcs_invalid_path_raises_valueerror(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + import struct_module.content_fetcher as mod + monkeypatch.setattr(mod, "gcs_available", True) + with pytest.raises(ValueError): + cf.fetch_content("gs://invalid-format") + + +def test_git_clone_error_bubbles(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + def fake_run(args, check): + if args[:2] == ["git", "clone"]: + raise subprocess.CalledProcessError(1, args) + monkeypatch.setattr(subprocess, "run", fake_run) + with pytest.raises(subprocess.CalledProcessError): + cf.fetch_content("github://owner/repo/main/file.txt") + + +def test_git_pull_error_bubbles(monkeypatch, tmp_path): + cf = ContentFetcher(cache_dir=tmp_path / "cache") + repo_dir = tmp_path / "cache" / "owner_repo_main" + # Simulate existing repo so it tries pull + repo_dir.mkdir(parents=True, exist_ok=True) + (repo_dir / "file.txt").write_text("x") + + def fake_run(args, check): + if args[:3] == ["git", "-C", str(repo_dir)]: + raise subprocess.CalledProcessError(1, args) + monkeypatch.setattr(subprocess, "run", fake_run) + + with pytest.raises(subprocess.CalledProcessError): + cf.fetch_content("githubhttps://owner/repo/main/file.txt") From 39de4a77da0a0c829ad585f8565f96d903e3e1bd Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 9 Aug 2025 21:22:32 -0300 Subject: [PATCH 4/5] ci: keep coverage config but remove fail-under threshold; continue uploading to Codecov --- .coveragerc | 10 ++++++++++ .github/workflows/test-script.yaml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..27087bb --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +branch = True +source = struct_module + +[report] +omit = + tests/* + */__init__.py +show_missing = True +skip_covered = True diff --git a/.github/workflows/test-script.yaml b/.github/workflows/test-script.yaml index 95e6e9e..438f349 100644 --- a/.github/workflows/test-script.yaml +++ b/.github/workflows/test-script.yaml @@ -42,7 +42,7 @@ jobs: shell: bash run: | echo "REPORT_FILE=${REPORT_OUTPUT}" >> "$GITHUB_ENV" - pytest --cov --cov-branch --cov-report=xml -v --md-report --md-report-flavor gfm --md-report-exclude-outcomes passed skipped xpassed --md-report-output "$REPORT_OUTPUT" --pyargs tests + pytest --cov=struct_module --cov-branch --cov-report=xml -v --md-report --md-report-flavor gfm --md-report-exclude-outcomes passed skipped xpassed --md-report-output "$REPORT_OUTPUT" --pyargs tests - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 From 5c470b6518541a31e2f2b8e890e9ceb03c240d16 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 9 Aug 2025 21:47:16 -0300 Subject: [PATCH 5/5] tests(commands): cover more branches for generate, list, info, mcp, and validation error paths; all tests pass --- tests/test_commands_more.py | 181 ++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 tests/test_commands_more.py diff --git a/tests/test_commands_more.py b/tests/test_commands_more.py new file mode 100644 index 0000000..20852f7 --- /dev/null +++ b/tests/test_commands_more.py @@ -0,0 +1,181 @@ +import argparse +import subprocess +from unittest.mock import patch, MagicMock + +import pytest + +from struct_module.commands.generate import GenerateCommand +from struct_module.commands.info import InfoCommand +from struct_module.commands.list import ListCommand +from struct_module.commands.mcp import MCPCommand +from struct_module.commands.validate import ValidateCommand + + +@pytest.fixture +def parser(): + return argparse.ArgumentParser() + + +def test_generate_creates_base_path_and_console_output(parser, tmp_path): + command = GenerateCommand(parser) + args = parser.parse_args(['struct-x', str(tmp_path / 'base')]) + + # Minimal config: one file item with string content to avoid fetch + config = {'files': [{'hello.txt': 'Hello'}], 'folders': []} + + # Ensure the input store file exists to avoid FileNotFoundError inside TemplateRenderer + store_dir = tmp_path / 'store' + store_dir.mkdir(parents=True, exist_ok=True) + with open(store_dir / 'input.json', 'w') as fh: + fh.write('{}') + + with patch.object(command, '_load_yaml_config', return_value=config), \ + patch('os.path.exists', side_effect=lambda p: False if str(tmp_path / 'base') in p else True), \ + patch('os.makedirs') as mock_makedirs, \ + patch('builtins.print') as mock_print: + # Choose console output to avoid writing files + args.output = 'file' # still triggers base path creation logic + args.input_store = str(store_dir / 'input.json') + args.dry_run = True + args.vars = None + args.backup = None + args.file_strategy = 'overwrite' + args.global_system_prompt = None + args.structures_path = None + args.non_interactive = True + + command.execute(args) + + mock_makedirs.assert_called() # base path created + mock_makedirs.assert_called() # base path created + + +def test_generate_pre_hook_failure_aborts(parser, tmp_path): + command = GenerateCommand(parser) + args = parser.parse_args(['struct-x', str(tmp_path)]) + + config = {'pre_hooks': ['exit 1'], 'files': []} + + def fake_run(cmd, shell, check, capture_output, text): + raise subprocess.CalledProcessError(1, cmd, output='', stderr='boom') + + with patch.object(command, '_load_yaml_config', return_value=config), \ + patch('subprocess.run', side_effect=fake_run), \ + patch.object(command, '_create_structure') as mock_create_structure: + command.execute(args) + mock_create_structure.assert_not_called() + + +def test_generate_mappings_file_not_found(parser, tmp_path): + command = GenerateCommand(parser) + args = parser.parse_args(['struct-x', str(tmp_path)]) + args.mappings_file = ['missing.yaml'] + + with patch('os.path.exists', return_value=False): + # Should return early without error + command.execute(args) + + +def test_info_nonexistent_file_logs_error(parser): + command = InfoCommand(parser) + args = parser.parse_args(['does-not-exist']) + + with patch('os.path.exists', return_value=False): + # Should just log error and return without exception + command.execute(args) + + +def test_list_with_custom_structures_path(parser, tmp_path): + command = ListCommand(parser) + args = parser.parse_args(['-s', str(tmp_path / 'custom')]) + + custom = str(tmp_path / 'custom') + contribs = '/path/to/contribs' + + def mock_join(*parts): + # emulate join used in list._list_structures + if parts[-1] == '..': + return '/path/to' # dir of commands + if parts[-1] == 'contribs': + return contribs + return '/'.join(parts) + + walk_map = { + custom: [(custom, [], ['a.yaml'])], + contribs: [(contribs, [], ['b.yaml'])], + } + + def mock_walk(path): + return walk_map.get(path, []) + + with patch('os.path.dirname', return_value='/path/to/commands'), \ + patch('os.path.realpath', return_value='/path/to/commands'), \ + patch('os.path.join', side_effect=mock_join), \ + patch('os.walk', side_effect=mock_walk), \ + patch('builtins.print') as mock_print: + command._list_structures(args) + mock_print.assert_called() # printed list + + +def test_mcp_command_server_flag(parser): + command = MCPCommand(parser) + args = parser.parse_args(['--server']) + + async def fake_start(): + return None + + with patch.object(command, '_start_mcp_server', side_effect=fake_start) as mock_start: + command.execute(args) + mock_start.assert_called_once() + + +# ValidateCommand error-path tests on helpers + +def test_validate_structure_config_errors(parser): + v = ValidateCommand(parser) + with pytest.raises(ValueError): + v._validate_structure_config('not-a-list') + with pytest.raises(ValueError): + v._validate_structure_config(["not-a-dict"]) # non-dict item + with pytest.raises(ValueError): + v._validate_structure_config([{123: 'abc'}]) # non-str name + with pytest.raises(ValueError): + v._validate_structure_config([{ 'x': 123 }]) # non-str/non-dict content + with pytest.raises(ValueError): + v._validate_structure_config([{ 'x': {} }]) # dict missing keys + + +def test_validate_folders_config_errors(parser): + v = ValidateCommand(parser) + with pytest.raises(ValueError): + v._validate_folders_config('not-a-list') + with pytest.raises(ValueError): + v._validate_folders_config(["not-a-dict"]) # non-dict item + with pytest.raises(ValueError): + v._validate_folders_config([{123: {}}]) # non-str name + with pytest.raises(ValueError): + v._validate_folders_config([{ 'name': 'not-a-dict' }]) + with pytest.raises(ValueError): + v._validate_folders_config([{ 'name': {} }]) # missing 'struct' + with pytest.raises(ValueError): + v._validate_folders_config([{ 'name': { 'struct': 10 } }]) # invalid type + with pytest.raises(ValueError): + v._validate_folders_config([{ 'name': { 'struct': 'x', 'with': 'not-dict' } }]) + + +def test_validate_variables_config_errors(parser): + v = ValidateCommand(parser) + with pytest.raises(ValueError): + v._validate_variables_config('not-a-list') + with pytest.raises(ValueError): + v._validate_variables_config(["not-a-dict"]) # non-dict item + with pytest.raises(ValueError): + v._validate_variables_config([{123: {}}]) # non-str name + with pytest.raises(ValueError): + v._validate_variables_config([{ 'name': 'not-a-dict' }]) + with pytest.raises(ValueError): + v._validate_variables_config([{ 'name': {} }]) # missing type + with pytest.raises(ValueError): + v._validate_variables_config([{ 'name': { 'type': 'bad' } }]) + with pytest.raises(ValueError): + v._validate_variables_config([{ 'name': { 'type': 'boolean', 'default': 'yes' } }])