From 30965388e5a21e391e48ba348d293ed09526aa81 Mon Sep 17 00:00:00 2001 From: gospodima Date: Tue, 20 Jan 2026 21:48:59 +0100 Subject: [PATCH 1/4] test: add some integration and unit tests --- Cargo.lock | 410 ++++++++++++++++++------------------------ Cargo.toml | 12 +- src/checks/email.rs | 18 ++ src/checks/image.rs | 23 +++ src/checks/section.rs | 33 ++++ tests/cli_tests.rs | 110 ++++++++++++ 6 files changed, 370 insertions(+), 236 deletions(-) create mode 100644 tests/cli_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 7ec97be..5191200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -43,29 +43,50 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys", +] + +[[package]] +name = "assert_cmd" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bstr" @@ -74,20 +95,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", + "regex-automata", "serde", ] [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -105,9 +127,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -129,9 +151,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -141,11 +163,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -187,6 +209,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "either" version = "1.15.0" @@ -223,7 +251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -232,25 +260,34 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "getopts" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", ] [[package]] @@ -296,28 +333,28 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -326,9 +363,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.176" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" @@ -347,14 +384,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mdrefcheck" version = "0.2.0" dependencies = [ + "assert_cmd", "clap", "clap-verbosity-flag", "colored", @@ -364,6 +402,7 @@ dependencies = [ "log", "path-clean", "pathdiff", + "predicates", "pulldown-cmark", "rayon", "regex", @@ -373,9 +412,24 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] [[package]] name = "once_cell" @@ -385,9 +439,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot_core" @@ -399,7 +453,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -416,9 +470,9 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -429,11 +483,41 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -459,9 +543,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -526,21 +610,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -601,9 +685,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -612,34 +696,40 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" @@ -648,48 +738,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "walkdir" -version = "2.5.0" +name = "wait-timeout" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ - "same-file", - "winapi-util", + "libc", ] [[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "wasip2", + "same-file", + "winapi-util", ] [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -698,153 +782,15 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.3", + "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" diff --git a/Cargo.toml b/Cargo.toml index 3416300..410f8af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,21 +12,25 @@ keywords = ["markdown", "cli", "reference", "link-checker", "docs"] categories = ["command-line-utilities", "development-tools"] [dependencies] -clap = { version = "4.5.51", features = ["derive"] } +clap = { version = "4.5.54", features = ["derive"] } clap-verbosity-flag = "3.0.4" -colored = "3.0.0" +colored = "3.1.1" dashmap = "6.1.0" env_logger = "0.11.8" ignore = "0.4.25" -log = "0.4.28" +log = "0.4.29" path-clean = "1.0.1" pathdiff = "0.2.3" pulldown-cmark = "0.13.0" rayon = "1.11.0" regex = "1.12.2" -tempfile = "3.23.0" walkdir = "2.5.0" +[dev-dependencies] +tempfile = "3" +assert_cmd = "2" +predicates = "3" + [package.metadata.release] pre-release-replacements = [ { file = "pyproject.toml", search = 'version = "[a-z0-9\\.-]+"', replace = 'version = "{{version}}"' }, diff --git a/src/checks/email.rs b/src/checks/email.rs index f66d750..ef0451a 100644 --- a/src/checks/email.rs +++ b/src/checks/email.rs @@ -13,3 +13,21 @@ fn is_valid_email(s: &str) -> bool { static EMAIL_RE: &str = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"; Regex::new(EMAIL_RE).unwrap().is_match(s) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn valid_emails() { + assert!(is_valid_email("user@example.com")); + assert!(is_valid_email("user.name+tag+sorting@example.co.uk")); + assert!(validate_email("user@example.com").is_ok()); + } + + #[test] + fn invalid_emails() { + assert!(!is_valid_email("not-an-email")); + assert!(validate_email("not-an-email").is_err()); + } +} diff --git a/src/checks/image.rs b/src/checks/image.rs index 85a5a03..8c8caca 100644 --- a/src/checks/image.rs +++ b/src/checks/image.rs @@ -16,3 +16,26 @@ pub fn validate_image(current_path: &Path, dest: &str) -> Result<(), String> { Err(format!("Image not found: {dest}")) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn image_local_found_and_missing() { + let dir = tempdir().unwrap(); + let cur = dir.path().join("cur.md"); + fs::write(&cur, "# hi").unwrap(); + + let img = dir.path().join("img.png"); + fs::write(&img, "data").unwrap(); + + // relative to current file + assert!(validate_image(&cur, "img.png").is_ok()); + + // missing + assert!(validate_image(&cur, "missing.png").is_err()); + } +} diff --git a/src/checks/section.rs b/src/checks/section.rs index 3b7d2f5..3ddf10f 100644 --- a/src/checks/section.rs +++ b/src/checks/section.rs @@ -40,3 +40,36 @@ pub fn validate_section_link( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::sync::Arc; + use tempfile::tempdir; + + #[test] + fn validate_existing_and_missing_headings() { + let dir = tempdir().unwrap(); + let cur = dir.path().join("cur.md"); + let tgt = dir.path().join("tgt.md"); + + fs::write(&cur, "[link](tgt.md#intro)").unwrap(); + fs::write(&tgt, "# Intro\n## Other").unwrap(); + + let map = Arc::new(parser::SectionLinkMap::new()); + + // valid + assert!(validate_section_link(&cur, "tgt.md#intro", &map).is_ok()); + + // missing heading + let err = validate_section_link(&cur, "tgt.md#missing", &map); + assert!(err.is_err()); + assert!(err.err().unwrap().contains("Missing heading")); + + // missing file + let err2 = validate_section_link(&cur, "nope.md#h", &map); + assert!(err2.is_err()); + assert!(err2.err().unwrap().contains("File not found")); + } +} diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs new file mode 100644 index 0000000..392ca1e --- /dev/null +++ b/tests/cli_tests.rs @@ -0,0 +1,110 @@ +use predicates::prelude::*; +use std::fs; + +#[test] +fn test_help_flag() { + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg("--help"); + cmd.assert() + .success() + .stdout(predicate::str::contains("Check markdown references")); +} + +#[test] +fn test_fails_on_broken_links() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("bad.md"); + fs::write(&file_path, "[broken link](./nonexistent.md)").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + + cmd.assert() + .failure() + .stdout(predicate::str::contains("File not found")); +} + +#[test] +fn test_succeeds_on_valid_links() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("good.md"); + let target_path = dir.path().join("target.md"); + + fs::write(&file_path, "[valid link](./target.md)").unwrap(); + fs::write(&target_path, "# Target").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + + cmd.assert().success(); +} + +#[test] +fn test_image_local_missing_cli() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("img2.md"); + fs::write(&file_path, "![img](./missing.png)").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + + cmd.assert() + .failure() + .stdout(predicate::str::contains("Image not found")); +} + +#[test] +fn test_mailto_invalid_cli() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("mail.md"); + fs::write(&file_path, "[contact](mailto:not-an-email)").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + + cmd.assert() + .failure() + .stdout(predicate::str::contains("Invalid email")); +} + +#[test] +fn test_heading_anchor_cli() { + let dir = tempfile::tempdir().unwrap(); + let a = dir.path().join("a.md"); + let b = dir.path().join("b.md"); + + fs::write(&b, "# Intro").unwrap(); + fs::write(&a, "[goto](./b.md#intro)").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + cmd.assert().success(); +} + +#[test] +fn test_exclude_path_cli() { + let dir = tempfile::tempdir().unwrap(); + let bad = dir.path().join("bad.md"); + fs::write(&bad, "[broken](./missing.md)").unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + // exclude the input root itself -> scanner pre-filters and returns no files + cmd.arg("--exclude").arg(dir.path()).arg(dir.path()); + + // excluded input should result in no files scanned + cmd.assert().success(); +} + +#[test] +fn test_ignore_regex_skips_validation_cli() { + let dir = tempfile::tempdir().unwrap(); + let f = dir.path().join("x.md"); + fs::write(&f, "[skipme](skip:whatever)").unwrap(); + + // without ignore, this would error (treated as file path) + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg("--ignore").arg("^skip:").arg(dir.path()); + + // --ignore should prevent validation of that link + cmd.assert().success(); +} From 4b15104d77d1943649fea6d2b988f0eb10ff9236 Mon Sep 17 00:00:00 2001 From: gospodima Date: Sat, 24 Jan 2026 14:20:42 +0100 Subject: [PATCH 2/4] test: add more tests --- src/scanner.rs | 36 ++++++++++++++++ tests/{cli_tests.rs => integration_tests.rs} | 45 ++++++++++++++++++++ 2 files changed, 81 insertions(+) rename tests/{cli_tests.rs => integration_tests.rs} (70%) diff --git a/src/scanner.rs b/src/scanner.rs index 37bf7f4..6b7192e 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -103,3 +103,39 @@ pub fn gather_markdown_files( .map(|entry| entry.path().to_path_buf()) .collect() } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn returns_empty_on_no_paths() { + let r = gather_markdown_files(&[], &[], true); + assert!(r.is_empty()); + } + + #[test] + fn finds_markdown_in_dir() { + let dir = tempdir().unwrap(); + let md = dir.path().join("foo.md"); + fs::write(&md, "# hi").unwrap(); + + let res = gather_markdown_files(&[dir.path().to_path_buf()], &[], true); + assert!(res.iter().any(|p| p.ends_with("foo.md"))); + } + + #[test] + fn excluding_input_root_yields_no_files() { + let dir = tempdir().unwrap(); + let sub = dir.path().join("sub"); + std::fs::create_dir_all(&sub).unwrap(); + let f = sub.join("a.md"); + fs::write(&f, "# hello").unwrap(); + + let single = vec![sub.clone()]; + let res = gather_markdown_files(&single, &single, true); + assert!(res.is_empty()); + } +} diff --git a/tests/cli_tests.rs b/tests/integration_tests.rs similarity index 70% rename from tests/cli_tests.rs rename to tests/integration_tests.rs index 392ca1e..2674b40 100644 --- a/tests/cli_tests.rs +++ b/tests/integration_tests.rs @@ -108,3 +108,48 @@ fn test_ignore_regex_skips_validation_cli() { // --ignore should prevent validation of that link cmd.assert().success(); } + +#[test] +fn test_multiple_headings_cli() { + let dir = tempfile::tempdir().unwrap(); + let tgt = dir.path().join("tgt.md"); + let src = dir.path().join("src.md"); + + fs::write(&tgt, "# One\n## Two\n### Three").unwrap(); + fs::write( + &src, + "[a](./tgt.md#one)\n[b](./tgt.md#two)\n[c](./tgt.md#three)", + ) + .unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + cmd.assert().success(); +} + +#[test] +fn test_repeated_headings_cli() { + let dir = tempfile::tempdir().unwrap(); + let tgt = dir.path().join("tgt.md"); + let src = dir.path().join("src.md"); + + // three identical headings -> anchors: intro, intro-1, intro-2 + fs::write(&tgt, "# Intro\n# Intro\n# Intro").unwrap(); + fs::write( + &src, + "[a](./tgt.md#intro)\n[b](./tgt.md#intro-1)\n[c](./tgt.md#intro-2)", + ) + .unwrap(); + + let mut cmd = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd.arg(dir.path()); + cmd.assert().success(); + + // referencing a non-existent numbered anchor should fail + fs::write(&src, "[d](./tgt.md#intro-3)").unwrap(); + let mut cmd2 = assert_cmd::cargo::cargo_bin_cmd!("mdrefcheck"); + cmd2.arg(dir.path()); + cmd2.assert() + .failure() + .stdout(predicate::str::contains("Missing heading")); +} From e19f76e6b198f268275f874005b4a24f8f9cf257 Mon Sep 17 00:00:00 2001 From: gospodima Date: Sat, 24 Jan 2026 14:28:29 +0100 Subject: [PATCH 3/4] ci: rename cargo token --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a33cd4..cccc6e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,7 +155,7 @@ jobs: - name: Publish to crates.io env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN_PUBNEW }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish --no-verify - uses: actions/download-artifact@v4 From 6c125c738806b07e31490823c7ae86f3c6569cb4 Mon Sep 17 00:00:00 2001 From: gospodima Date: Sat, 24 Jan 2026 14:38:26 +0100 Subject: [PATCH 4/4] ci: update macos and ubuntu images --- .github/workflows/ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cccc6e1..fe8df9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,17 +33,17 @@ jobs: strategy: matrix: platform: - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: x86_64 - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: x86 - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: aarch64 - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: armv7 - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: s390x - - runner: ubuntu-22.04 + - runner: ubuntu-latest target: ppc64le steps: - uses: actions/checkout@v4 @@ -61,7 +61,7 @@ jobs: path: dist musllinux: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest strategy: matrix: target: [x86_64, x86, aarch64, armv7] @@ -108,9 +108,9 @@ jobs: strategy: matrix: platform: - - runner: macos-13 + - runner: macos-15-intel target: x86_64 - - runner: macos-14 + - runner: macos-latest target: aarch64 steps: - uses: actions/checkout@v4