DTgMK8e
zawCOT-+51XFSUho?@u6;d?mtMj<0>KWPM=<#p=yQs(H2=*p(3C^uKTVH9btQTw=R&
zz%L=td@fyfMh!u0XItYrAVsnEf`gB>RO(YPO;0+UydA)JwIw*^*Re*)txH)X=|HD2
zrBhH)<4r*t$d#@;G~iS2n_*KEwV%SeS*~}#l>$3+_C4+GvKyj8EMTNQ)lx*B0gh?O
zFr1oO2)4HOb6Nos6J52bX(@}}37KMyML>2sQ<;2Qfq9|Uu3~#_A~yXXaj$bzY1YeV
z9ZZ%PtUxCa&*WBt63phHExrX4O0rUwZ5O`sAdcRu=+g>)2R-A4Iv4%J=y1s`EZ-dk
zZhn^_W^wtr7#dTFnS-%w?2U%26MzX`@;jlbwt9r#77MBreX*bnub%u4#zxEVG%k(?
zy>aa@&k>zOy^7ZZIFUf4cVjoxV0qLrgRn^l9i)aP@t6Z+Nkuiha2zYuTDbz&SkWC)
zdnUbs{N)=84u8d0g7VYaN(GEkX!eXam8x7TDWUiXQSebEbMk3uuv@h+M+Gz99}F9t
z)Xw)86j=qC9l4e>ZX)@W8~UJclg9&FWOL;|DjY3w`O|2|5z0BnvEV7}e3`Gz)UYT9N8QXk1D>|DQs
zstuq}6l{U!mGvK@!90M{#&Ju!A2@;ddQyrXEWv%Ebbw4Gy3}x5>%?q_u{=wxr)+An
z$KIaC1Y=OarwT?K%neNO`B=f~-)s`3_u_3%SPYTUBBqjLgZG5CJ>h9y5Z9;E0~cWR
z(eXFjI_E~4;I^EO8!h>}Zmg$oj>8+C%WU6qmu_k!@X9i%Xhvk(TV(p>S|8uwt%QD-
zy}_*^W%NT@^T3pAV~JZSiKq2lr64QjLs)P~9qSXx8dALnb1KefEq@V>q}*zPfMrgV
zQB5G`Fr)l>s5-Xv6%0a-%{t^T2Dcfd2w7K1QJZwrde3Gl?cY!0lQ<#9Wn+xV%!$){
z8Hyw92@%~L_{<)0NeubI71LH@ZMlS*TX4MeCNyMBE5YG?utAZ9`+I80{ft_-yKA3(
z1L;fZ3+hwZtSF2n9Qn;NILZwZ@UmLQHqJZvuH?c)YJ}^TpZBMe*GZ^#q79l6TRYo_
z;b(`L%0kW^7$gp>Ywi&Yj=?q|$dpbN%N@D^F+v=Pv5;Qhh|h3+zV$KPDb*>NqVMKx
zD1+Tb9R2SOGRN3oW?5Z~REj{%xmO8z>S;ieO$;It`Zc?GUnByM7LS81ukN^Hp5#86
z_|T9=xZUo(n!^LlNulY|_+w67XR`q^>ePM+&jIlT}T?D%mJvQ?lkwAPo7
zkrj@YzFtI?+IG~FH{M$cp1_oFmph8+Y98fkIm1Ppn{g8X@`*CTn_Yzp0}@lj_f_cG8#|uFkxkM~e5sS#N0v&CS$TO%6LzXI
zd8{bBHdT9R1`#ZqCG=o^%=L{UeW?I}5_$#(hKBa8a&ms+72;Exz`;&~osIyk;P1qN
ziLPM&Xoiho<~bgHZJ0@CWsUw%5=uT`0gMLh(A3Nesq0hwk29s6MYR~O(&r5?w0hZ5
z7I(SNVzVMv&NoUmew|uJHMGAZC}zCUe9AQT2c0il+;5S4?XqN
z?~r~?O_i6XV=#}Eh0kMUf&7@7tn`3p#zuDb^tOh^gOP(kA4b$qM?AhpL(^{=U|?*i
z`fANnTiE9IRR-nbz*$!IFK!eD>A5@6V7;K8c16w-`I#FcU6YK
zBM=4)nw_t^<8cnHA{eX4+3%%=xm-RLXA4~UY2}D|)$)!0)x_amZ-Wuu_kFVW!LO5+
z>|}{KljCenKUnS7Z5;Q>;L$y%4H=$Z3_#A=&|YpVJ=FABa*sB3Jv4B281Fn;B8$>Z
zVm_4Ft><=m*t(}5e!yKVsdN;!d^?nB0twryJS9>REBSS~5@s7YxnTf+G>U&Mmz|Nl
zy&2HtuUvkhu3O}ysbYG`xAUfN!q(>;}HA&&HK
z*$w7Q8iNi0Srxuf}1w?3M-wG%V5!>Vi^_(ilORI0CMh#E$1Gm@6
zY;`-crI!Urhd40>pRJhKa;-#X-6$jA6ds`oo5`bdza-NhJwzGI*`|Co}_Rozu2+7qv1+%Yz5E@yV#IgRI>_MiCK`=ONSs
zKH3XlT0H}zpZ_IQ3Rz>2Rj3?}pO%x1Bz>GcARDh%=sK4*`0XV)31AjRL=AE5bB0N$
zB3ki0|7g?a7ez+0;Azt{{bGjwl)rx196_m~$m2M*Ow8emkY&4gCzUCpY9D@g+
z*()hyuUd$>&csGv0yB^LNzNu45P%?C6VSKcs}jX(!r4ri`CSfsHoXAssI*P
zIGZe{bZ)PgHVmOyp>ck^-<}S!OHGlr@Q6%%bsc@rIl_KQm+Bohx-N$K$(?!Ejn}yZ
z?&y5Ek?{6mS3uqAJxxgPS{yLzIR2ALQqmT_C{s0*qpCu2D;D@}1srW?Zr_w@Rf_T`
z-6gd=r%j!7KzBCqxpJlrCH;a9PCCqPzU186NVq%(JnKBUym}|p@^)Q{-!2`bTYH$!
zL-)BkPYbTY>)XdMm?`U+TLT-J-MCXy!Rr1aVlr55P4=R})QzoUr
zb3l+X(5*m2JBWK0Jd{#X9pm?;Z~-!>g{;WkW|pT{qIdj(7des>GG#t2SkpLhqa*4h
zZPHTg>ubd+y$d#Up>X8A?M&D=>{w%CBwAkS_?B30J{X=Od1oAZqAq&Bt#`PZ?^ja%
zd8r25xRRnh&mlOx;CasAlzn-ufLkhY+)mR
zL;glG{szrmKLI;33fuJqlZGs-&rNWztPS!BInckw|Ml#mj{{jO-o4~E*|iO;FJ(pR
zzKm(mB~S?|C=1x+q*ymk8-tAMe3|{7q_4}vAh{xiI7~W^AXM?meXtEo%mG9tw
z4YF9AP{k~dIqCJI^7knG0i;9}1?b*Mi!%J5-s$`xpx>l9cnpv#}#$^c^o?_Lse^Q?BiW{
zTov%o0PAPLh{ayQw(5!-ggJB7dYlQhn;`rssQ8&o*QB#0|#svJcdH%T6e{27?(tk(&Zjb+j
zC_bM4lUe>d%TE*l@I%!9+14Lz_uu93zf1hT&fzEScOLvlq2za-zf1oA%=1IK|JfA(
z!1JpG@c+sb@&~40ErEY#`XST*Y(0Nq`e_mTGs~|leg6lRzgq_X%=7DL*b)BcO8>MF
z{+Z>6iSV;o{(5=6Z%K_$J
zmcws$!_V*bpVhjbbKH9NSYiKf?e2Hf?;_DpR1WIjC8OVQzgO@-aRM)Xt>=G7{$9-f
tL|Q+t!rx2V-;uvZ=%2{R$A7*2F<#3`!aOQq007$K&Eqk!bN{vb{|k$pI.
+
+import io
+
+import pytest
+
+from . import testutil
+
+from conservancy_beancount.tools import extract_odf_links
+
+SRC_PATH = testutil.test_path('repository/LinksReport.ods')
+
+EXPECTED_FILE_LINKS = {
+ '/repository/Projects/project-data.yml',
+ str(testutil.test_path('repository/Projects/project-data.yml')),
+ str(testutil.test_path('repository/Projects/Bad Link.txt')),
+}
+
+@pytest.mark.parametrize('arglist,sep', [
+ (['-0'], '\0'),
+ (['-d', '\\v'], '\v'),
+ ([str(SRC_PATH)], '\n'), # Test that links aren't duplicated
+])
+def test_extract_file_links(arglist, sep, caplog):
+ arglist.append(str(SRC_PATH))
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+ exitcode = extract_odf_links.main(arglist, stdout, stderr)
+ assert exitcode == 0
+ assert not stderr.getvalue()
+ actual = stdout.getvalue().split(sep)
+ if actual and not actual[-1]:
+ actual.pop()
+ assert len(actual) == len(EXPECTED_FILE_LINKS)
+ assert set(actual) == EXPECTED_FILE_LINKS
+ assert caplog.records
+ assert any(
+ log.levelname == 'WARNING'
+ and log.message.endswith('/Bad Link.txt not found')
+ for log in caplog.records
+ )