Skip to main content

git_checks/
lfs_pointer.rs

1// Copyright Kitware, Inc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11use lazy_static::lazy_static;
12use regex::Regex;
13
14/// A check which verifies that files meant to be under LFS control are valid LFS pointers.
15///
16/// This is based on the `filter` attribute for a file being set to `lfs`.
17#[derive(Builder, Debug, Default, Clone, Copy)]
18#[builder(field(private))]
19pub struct LfsPointer {}
20
21impl LfsPointer {
22    /// Create a new builder.
23    pub fn builder() -> LfsPointerBuilder {
24        Default::default()
25    }
26
27    fn check_line(
28        prev_key: Option<String>,
29        key: &str,
30        value: &str,
31        content: &dyn Content,
32        name: &str,
33    ) -> CheckResult {
34        let mut result = CheckResult::new();
35
36        if let Some(old_key) = prev_key {
37            if old_key == "version" {
38                // Ignore; the first non-version key is always fine.
39            } else if key < old_key.as_str() {
40                result.add_error(format!(
41                    "{}unsorted key `{key}` found in LFS pointer `{name}`.",
42                    commit_prefix_str(content, "not allowed;"),
43                ));
44            }
45        } else if key == "version" {
46            if value != GIT_LFS_SPEC_URL {
47                result.add_warning(format!(
48                    "{}unexpected git-lfs version string in `{name}`: `{value}`.",
49                    commit_prefix_str(content, "contains an"),
50                ));
51            }
52        } else {
53            result.add_error(format!(
54                "{}the first key in LFS pointer `{name}` must be `version`; found `{key}`.",
55                commit_prefix_str(content, "not allowed;"),
56            ));
57        }
58
59        match key {
60            "oid" => {
61                let mut split = value.splitn(2, ':');
62                let algo = split.next();
63                let digest = split.next();
64
65                const fn ascii_hex_len(bits: usize) -> usize {
66                    // bits / 8 (bits per byte) * 2 (hex digits per byte)
67                    bits >> 2
68                }
69
70                fn is_ascii_hex(digest: &str) -> bool {
71                    digest
72                        .bytes()
73                        .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
74                }
75
76                let mut check_algo_digest = |algo, digest: &str, bits| {
77                    if digest.is_empty() {
78                        result.add_error(format!(
79                            "{}empty digest for {algo} algorithm in LFS pointer `{name}`.",
80                            commit_prefix_str(content, "not allowed;"),
81                        ));
82                    } else if digest.len() != ascii_hex_len(bits) {
83                        result.add_error(format!(
84                            "{}invalid digest length in `{digest}` for {algo} algorithm in LFS pointer `{name}`.",
85                            commit_prefix_str(content, "not allowed;"),
86                        ));
87                    } else if !is_ascii_hex(digest) {
88                        result.add_error(format!(
89                            "{}invalid digest content `{digest}` for {algo} algorithm in LFS pointer `{name}`.",
90                            commit_prefix_str(content, "not allowed;"),
91                        ));
92                    }
93                };
94
95                match algo {
96                    Some("") | None => {
97                        result.add_error(format!(
98                            "{}missing hash algorithm in LFS pointer `{name}`.",
99                            commit_prefix_str(content, "not allowed;"),
100                        ));
101                    },
102                    Some(algo) => {
103                        if let Some(digest) = digest {
104                            match algo {
105                                algo @ "sha256" => check_algo_digest(algo, digest, 256),
106                                algo => {
107                                    result.add_warning(format!(
108                                        "{}unrecognized hash algorithm `{algo}` in LFS pointer `{name}`.",
109                                        commit_prefix_str(content, "contains an"),
110                                    ));
111
112                                    if digest.is_empty() {
113                                        result.add_error(format!(
114                                            "{}empty digest for {algo} algorithm in LFS pointer `{name}`.",
115                                            commit_prefix_str(content, "not allowed;"),
116                                        ));
117                                    }
118                                },
119                            }
120                        } else {
121                            result.add_error(format!(
122                                "{}missing digest for {algo} algorithm in LFS pointer `{name}`.",
123                                commit_prefix_str(content, "not allowed;"),
124                            ));
125                        }
126                    },
127                }
128            },
129            "size" => {
130                if let Ok(value) = value.parse::<u64>() {
131                    if value == 0 {
132                        result.add_error(format!(
133                            "{}the `size` value in LFS pointer `{name}` must be greater than 0; \
134                             found `{value}`.",
135                            commit_prefix_str(content, "not allowed;"),
136                        ));
137                    }
138                } else {
139                    result.add_error(format!(
140                        "{}the `size` key value in LFS pointer `{name}` must be an unsigned \
141                         integer; found `{value}`.",
142                        commit_prefix_str(content, "not allowed;"),
143                    ));
144                }
145            },
146            key => {
147                fn is_valid_key(key: &str) -> bool {
148                    key.bytes().all(|b| {
149                        b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'.' || b == b'-'
150                    })
151                }
152
153                if !is_valid_key(key) {
154                    // This is not actually reachable without changing the regular expression which
155                    // only matches valid keys.
156                    result.add_error(format!(
157                        "{}the key `{key}` is not valid in LFS pointer `{name}`",
158                        commit_prefix_str(content, "not allowed;"),
159                    ));
160                }
161            },
162        }
163
164        result
165    }
166}
167
168const GIT_LFS_SPEC_URL: &str = "https://git-lfs.github.com/spec/v1";
169
170lazy_static! {
171    static ref LFS_LINE_RE: Regex = Regex::new(
172        "^(?P<key>[a-z0-9.-]*) \
173         (?P<value>[^\r\n]*)$",
174    )
175    .unwrap();
176}
177
178impl ContentCheck for LfsPointer {
179    fn name(&self) -> &str {
180        "lfs-pointer"
181    }
182
183    fn check(
184        &self,
185        ctx: &CheckGitContext,
186        content: &dyn Content,
187    ) -> Result<CheckResult, Box<dyn Error>> {
188        let mut result = CheckResult::new();
189
190        for diff in content.diffs() {
191            match diff.status {
192                StatusChange::Added | StatusChange::Modified(_) => (),
193                _ => continue,
194            }
195
196            let filter_attr = ctx.check_attr("filter", diff.name.as_path())?;
197            if let AttributeState::Value(filter_name) = filter_attr {
198                if filter_name != "lfs" {
199                    continue;
200                }
201            } else {
202                continue;
203            }
204
205            let cat_file = ctx
206                .git()
207                .arg("cat-file")
208                .arg("blob")
209                .arg(diff.new_blob.as_str())
210                .output()
211                .map_err(|err| GitError::subcommand("cat-file", err))?;
212            let lfs_pointer = if let Ok(lfs_pointer) = String::from_utf8(cat_file.stdout) {
213                lfs_pointer
214            } else {
215                result.add_error(format!(
216                    "{}invalid utf-8 sequence in an LFS pointer added in `{}`.",
217                    commit_prefix_str(content, "not allowed;"),
218                    diff.name,
219                ));
220                continue;
221            };
222
223            // Empty files are valid LFS pointers (for empty files).
224            if lfs_pointer.is_empty() {
225                continue;
226            }
227
228            let (lfs_res, _, has_oid, has_size) = lfs_pointer.lines().enumerate().fold(
229                (CheckResult::new(), None, false, false),
230                |data, (count, line)| {
231                    let (mut lfs_res, prev_key, has_oid, has_size) = data;
232
233                    if let Some(lfs_line) = LFS_LINE_RE.captures(line) {
234                        let key = lfs_line
235                            .name("key")
236                            .expect("the LFS regex should have a 'key' group");
237                        let value = lfs_line
238                            .name("value")
239                            .expect("the LFS regex should have a 'value' group");
240
241                        if Some(key.as_str()) == prev_key.as_deref() {
242                            lfs_res.add_error(format!(
243                                "{}duplicate key `{}` in an LFS pointer added in `{}`.",
244                                commit_prefix_str(content, "not allowed;"),
245                                key.as_str(),
246                                diff.name,
247                            ));
248                        }
249
250                        let line_res = Self::check_line(
251                            prev_key,
252                            key.as_str(),
253                            value.as_str(),
254                            content,
255                            diff.name.as_str(),
256                        );
257
258                        let new_has_oid = has_oid || key.as_str() == "oid";
259                        let new_has_size = has_size || key.as_str() == "size";
260
261                        (
262                            lfs_res.combine(line_res),
263                            Some(key.as_str().into()),
264                            new_has_oid,
265                            new_has_size,
266                        )
267                    } else {
268                        lfs_res.add_error(format!(
269                            "{}invalid line in an LFS pointer added in `{}` on line {}.",
270                            commit_prefix_str(content, "not allowed;"),
271                            diff.name,
272                            count + 1,
273                        ));
274
275                        (lfs_res, prev_key, has_oid, has_size)
276                    }
277                },
278            );
279            result = result.combine(lfs_res);
280
281            if !has_oid {
282                result.add_error(format!(
283                    "{}an LFS pointer is missing the `oid` key in `{}`.",
284                    commit_prefix_str(content, "not allowed;"),
285                    diff.name,
286                ));
287            }
288
289            if !has_size {
290                result.add_error(format!(
291                    "{}an LFS pointer is missing the `size` key in `{}`.",
292                    commit_prefix_str(content, "not allowed;"),
293                    diff.name,
294                ));
295            }
296        }
297
298        Ok(result)
299    }
300}
301
302#[cfg(feature = "config")]
303pub(crate) mod config {
304    use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
305    use serde::Deserialize;
306    #[cfg(test)]
307    use serde_json::json;
308
309    use crate::LfsPointer;
310
311    /// Configuration for the `LfsPointer` check.
312    ///
313    /// No configuration available.
314    ///
315    /// This check is registered as a commit check with the name `"lfs_pointer"` and a topic check
316    /// with the name `"lfs_pointer/topic"`.
317    #[derive(Deserialize, Debug)]
318    pub struct LfsPointerConfig {}
319
320    impl IntoCheck for LfsPointerConfig {
321        type Check = LfsPointer;
322
323        fn into_check(self) -> Self::Check {
324            Default::default()
325        }
326    }
327
328    register_checks! {
329        LfsPointerConfig {
330            "lfs_pointer" => CommitCheckConfig,
331            "lfs_pointer/topic" => TopicCheckConfig,
332        },
333    }
334
335    #[test]
336    fn test_lfs_pointer_config_empty() {
337        let json = json!({});
338        let check: LfsPointerConfig = serde_json::from_value(json).unwrap();
339
340        let _ = check.into_check();
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use git_checks_core::{Check, TopicCheck};
347
348    use crate::test::*;
349    use crate::LfsPointer;
350
351    const LFS_INVALID_UTF8: &str = "c413d8954d903557de00be9d96b4daa264bd4b22";
352    const LFS_INVALID_LINE_FORMAT: &str = "709c2cd38863717231453bb4945897a0ebef3f80";
353    const LFS_MISSING_VERSION: &str = "0ec2ab0e229fdcbff18a8bfd55774e4f71b04bbb";
354    const LFS_MISPLACED_VERSION: &str = "c86f7b78fd6ca471d97f3861addf3a35c25f3504";
355    const LFS_INVALID_VERSION: &str = "f799c407f7678ba38f87720fb4217ece6c50f7e5";
356    const LFS_DUPLICATE_KEY: &str = "4d0ead1cac518874d1f7cd005569238b7b9bc7c3";
357    const LFS_MISSING_SIZE: &str = "e6f342338093141e365b1bb1596290c155198c60";
358    const LFS_MISSING_OID: &str = "ceeabe41a44a4469b6644017af725b0127d92302";
359    const LFS_UNRECOGNIZED_HASH: &str = "1aa2873adebe911caf594c376efc5c522f8d4c3a";
360    const LFS_MISSING_HASH: &str = "109806f98fd036cba88d07f94df162aad5086d0b";
361    const LFS_UNSORTED_KEYS: &str = "aaeedde2f188005c24e090bc44378b17770e3b94";
362    const LFS_EMPTY_POINTER: &str = "186cb934ce4b20f85ee6bfb834ab5652121e6436";
363    const LFS_VALID_POINTER: &str = "e088802e2abc6b7c2bb99e194bcc074a9cf9076c";
364    const LFS_SHA256_BAD_DIGEST_LEN: &str = "b00442c09d00aeb1ffabffea821e8b286da6b0df";
365    const LFS_SHA256_BAD_DIGEST_CONTENT: &str = "1cd551597d65f74a57928c024ff58edb532767e0";
366    const LFS_SHA256_EMPTY_DIGEST: &str = "6d377c8197b372df64689cb90db70904a2667dc9";
367    const LFS_UNKNOWN_ALGO_EMPTY_DIGEST: &str = "96c193edaf748309b7bb5c2e816abda4a06c80c0";
368    const LFS_MISSING_ALGO: &str = "6b4125e802283d716ceef0f4c7630a94810654fd";
369    const LFS_INVALID_KEY: &str = "335b860660b133c59c651b3cb22ac883fdb8d004";
370    const LFS_EMPTY_KEY: &str = "7516f6f1e68245da397fd459e0516ca3f2334aa5";
371    const LFS_DELETED_BAD_FILE: &str = "94c4f73c20e547ec9ea45b92a1b10b1b4e7d0505";
372    const LFS_NONINT_SIZE: &str = "3ed812f83b9bd5d8ed5538933254d07a42928e47";
373    const LFS_ZERO_SIZE: &str = "5523b49074a66611aaafcac32cf7d05a654f552c";
374    const LFS_NOT_LFS_FILTER: &str = "be247837cbdbcbbb8a7879c638c918e59bc61e56";
375
376    const LFS_INVALID_UTF8_FIXED: &str = "5997c958735f9d4eab0640fa54b4c094bd74e71b";
377    const LFS_INVALID_LINE_FORMAT_FIXED: &str = "501a70c9ff37a7ce9e7c853a737b93e3be6d73d1";
378    const LFS_MISSING_VERSION_FIXED: &str = "225108803b9a8679c48272dc83765ce5a6a281e1";
379    const LFS_MISPLACED_VERSION_FIXED: &str = "7f2c99eab3e1cb58e41bdfe0ae56dfa5c6529a26";
380    const LFS_INVALID_VERSION_FIXED: &str = "ce3f57bc200ebe52cce3b10580ce6adc76e830bd";
381    const LFS_DUPLICATE_KEY_FIXED: &str = "59dafc938b6fbf913624afd16b3732c79f88e4b5";
382    const LFS_MISSING_SIZE_FIXED: &str = "9f8196108b33fc91884be01b63dd82bb484b41be";
383    const LFS_MISSING_OID_FIXED: &str = "b88474b22be8e87d2276fb9e54bf30147638d9b2";
384    const LFS_UNRECOGNIZED_HASH_FIXED: &str = "68aaf7516f3f22705a87ad19799bd03a9c5ae999";
385    const LFS_MISSING_HASH_FIXED: &str = "31d34a4fb619de4e4877e426255bd206b4f579b9";
386    const LFS_UNSORTED_KEYS_FIXED: &str = "829033be4f407b6c9e472778364fbabb76431c2b";
387    const LFS_SHA256_BAD_DIGEST_LEN_FIXED: &str = "60a5483724576389baf6b5cd8300d5ce0c9ae2f3";
388    const LFS_SHA256_BAD_DIGEST_CONTENT_FIXED: &str = "ba2c9f71853b64a9460a95a5f6057c4fcabc59a4";
389    const LFS_SHA256_EMPTY_DIGEST_FIXED: &str = "b06ca3f0c77173164f9fbec13e1f2a2a6814b69c";
390    const LFS_UNKNOWN_ALGO_EMPTY_DIGEST_FIXED: &str = "d7acdabf44ba40589674e59dfce9e5b5515f951c";
391    const LFS_MISSING_ALGO_FIXED: &str = "081c396ca642026464c17ad193239316e3100439";
392    const LFS_INVALID_KEY_FIXED: &str = "40c8f64bd2f1da90fa7bfbae8aaae9bd654f94ca";
393    const LFS_EMPTY_KEY_FIXED: &str = "c4e2d178e6cfd82a82d7b9b68544a7e1ec50d17b";
394    const LFS_NONINT_SIZE_FIXED: &str = "442f3131aecf8993912ec0e26e48fa48ad924364";
395    const LFS_ZERO_SIZE_FIXED: &str = "e854ba50135b51b59dfd4b59cfcc6a8aae49b01b";
396
397    #[test]
398    fn test_lfs_pointer_builder_default() {
399        assert!(LfsPointer::builder().build().is_ok());
400    }
401
402    #[test]
403    fn test_lfs_pointer_name_commit() {
404        let check = LfsPointer::default();
405        assert_eq!(Check::name(&check), "lfs-pointer");
406    }
407
408    #[test]
409    fn test_lfs_pointer_name_topic() {
410        let check = LfsPointer::default();
411        assert_eq!(TopicCheck::name(&check), "lfs-pointer");
412    }
413
414    #[test]
415    fn test_lfs_invalid_utf8() {
416        let check = LfsPointer::default();
417        let result = run_check("test_lfs_invalid_utf8", LFS_INVALID_UTF8, check);
418        test_result_errors(result, &[
419            "commit c413d8954d903557de00be9d96b4daa264bd4b22 not allowed; invalid utf-8 sequence \
420             in an LFS pointer added in `invalid-utf8.lfs`.",
421        ]);
422    }
423
424    #[test]
425    fn test_lfs_invalid_utf8_topic() {
426        let check = LfsPointer::default();
427        let result = run_topic_check("test_lfs_invalid_utf8_topic", LFS_INVALID_UTF8, check);
428        test_result_errors(
429            result,
430            &["invalid utf-8 sequence in an LFS pointer added in `invalid-utf8.lfs`."],
431        );
432    }
433
434    #[test]
435    fn test_lfs_invalid_utf8_topic_fixed() {
436        let check = LfsPointer::default();
437        run_topic_check_ok(
438            "test_lfs_invalid_utf8_topic_fixed",
439            LFS_INVALID_UTF8_FIXED,
440            check,
441        )
442    }
443
444    #[test]
445    fn test_lfs_invalid_line_format() {
446        let check = LfsPointer::default();
447        let result = run_check(
448            "test_lfs_invalid_line_format",
449            LFS_INVALID_LINE_FORMAT,
450            check,
451        );
452        test_result_errors(result, &[
453            "commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
454             pointer added in `invalid-line-format.lfs` on line 2.",
455            "commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
456             pointer added in `invalid-line-format.lfs` on line 3.",
457            "commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
458             pointer added in `invalid-line-format.lfs` on line 4.",
459            "commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
460             pointer added in `invalid-line-format.lfs` on line 8.",
461        ]);
462    }
463
464    #[test]
465    fn test_lfs_invalid_line_format_topic() {
466        let check = LfsPointer::default();
467        let result = run_topic_check(
468            "test_lfs_invalid_line_format_topic",
469            LFS_INVALID_LINE_FORMAT,
470            check,
471        );
472        test_result_errors(
473            result,
474            &[
475                "invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 2.",
476                "invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 3.",
477                "invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 4.",
478                "invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 8.",
479            ],
480        );
481    }
482
483    #[test]
484    fn test_lfs_invalid_line_format_topic_fixed() {
485        let check = LfsPointer::default();
486        run_topic_check_ok(
487            "test_lfs_invalid_line_format_topic_fixed",
488            LFS_INVALID_LINE_FORMAT_FIXED,
489            check,
490        )
491    }
492
493    #[test]
494    fn test_lfs_missing_version() {
495        let check = LfsPointer::default();
496        let result = run_check("test_lfs_missing_version", LFS_MISSING_VERSION, check);
497        test_result_errors(result, &[
498            "commit 0ec2ab0e229fdcbff18a8bfd55774e4f71b04bbb not allowed; the first key in LFS \
499             pointer `missing-version.lfs` must be `version`; found `oid`.",
500        ]);
501    }
502
503    #[test]
504    fn test_lfs_missing_version_topic() {
505        let check = LfsPointer::default();
506        let result = run_topic_check("test_lfs_missing_version_topic", LFS_MISSING_VERSION, check);
507        test_result_errors(result, &[
508            "the first key in LFS pointer `missing-version.lfs` must be `version`; found `oid`.",
509        ]);
510    }
511
512    #[test]
513    fn test_lfs_missing_version_topic_fixed() {
514        let check = LfsPointer::default();
515        run_topic_check_ok(
516            "test_lfs_missing_version_topic_fixed",
517            LFS_MISSING_VERSION_FIXED,
518            check,
519        )
520    }
521
522    #[test]
523    fn test_lfs_misplaced_version() {
524        let check = LfsPointer::default();
525        let result = run_check("test_lfs_misplaced_version", LFS_MISPLACED_VERSION, check);
526        test_result_errors(result, &[
527            "commit c86f7b78fd6ca471d97f3861addf3a35c25f3504 not allowed; the first key in LFS \
528             pointer `misplaced-version.lfs` must be `version`; found `oid`.",
529        ]);
530    }
531
532    #[test]
533    fn test_lfs_misplaced_version_topic() {
534        let check = LfsPointer::default();
535        let result = run_topic_check(
536            "test_lfs_misplaced_version_topic",
537            LFS_MISPLACED_VERSION,
538            check,
539        );
540        test_result_errors(result, &[
541            "the first key in LFS pointer `misplaced-version.lfs` must be `version`; found `oid`.",
542        ]);
543    }
544
545    #[test]
546    fn test_lfs_misplaced_version_topic_fixed() {
547        let check = LfsPointer::default();
548        run_topic_check_ok(
549            "test_lfs_misplaced_version_topic_fixed",
550            LFS_MISPLACED_VERSION_FIXED,
551            check,
552        )
553    }
554
555    #[test]
556    fn test_lfs_invalid_version() {
557        let check = LfsPointer::default();
558        let result = run_check("test_lfs_invalid_version", LFS_INVALID_VERSION, check);
559        test_result_warnings(
560            result,
561            &[
562                "commit f799c407f7678ba38f87720fb4217ece6c50f7e5 contains an unexpected git-lfs \
563                 version string in `invalid-version.lfs`: `https://hawser.github.com/spec/v1`.",
564            ],
565        );
566    }
567
568    #[test]
569    fn test_lfs_invalid_version_topic() {
570        let check = LfsPointer::default();
571        let result = run_topic_check("test_lfs_invalid_version_topic", LFS_INVALID_VERSION, check);
572        test_result_warnings(
573            result,
574            &[
575                "unexpected git-lfs version string in `invalid-version.lfs`: \
576                 `https://hawser.github.com/spec/v1`.",
577            ],
578        );
579    }
580
581    #[test]
582    fn test_lfs_invalid_version_topic_fixed() {
583        let check = LfsPointer::default();
584        run_topic_check_ok(
585            "test_lfs_invalid_version_topic_fixed",
586            LFS_INVALID_VERSION_FIXED,
587            check,
588        )
589    }
590
591    #[test]
592    fn test_lfs_duplicate_key() {
593        let check = LfsPointer::default();
594        let result = run_check("test_lfs_duplicate_key", LFS_DUPLICATE_KEY, check);
595        test_result_errors(
596            result,
597            &[
598                "commit 4d0ead1cac518874d1f7cd005569238b7b9bc7c3 not allowed; duplicate key \
599                 `duplicate` in an LFS pointer added in `duplicate-key.lfs`.",
600            ],
601        );
602    }
603
604    #[test]
605    fn test_lfs_duplicate_key_topic() {
606        let check = LfsPointer::default();
607        let result = run_topic_check("test_lfs_duplicate_key_topic", LFS_DUPLICATE_KEY, check);
608        test_result_errors(
609            result,
610            &["duplicate key `duplicate` in an LFS pointer added in `duplicate-key.lfs`."],
611        );
612    }
613
614    #[test]
615    fn test_lfs_duplicate_key_topic_fixed() {
616        let check = LfsPointer::default();
617        run_topic_check_ok(
618            "test_lfs_duplicate_key_topic_fixed",
619            LFS_DUPLICATE_KEY_FIXED,
620            check,
621        )
622    }
623
624    #[test]
625    fn test_lfs_missing_size() {
626        let check = LfsPointer::default();
627        let result = run_check("test_lfs_missing_size", LFS_MISSING_SIZE, check);
628        test_result_errors(
629            result,
630            &[
631                "commit e6f342338093141e365b1bb1596290c155198c60 not allowed; an LFS pointer is \
632                 missing the `size` key in `missing-size.lfs`.",
633            ],
634        );
635    }
636
637    #[test]
638    fn test_lfs_missing_size_topic() {
639        let check = LfsPointer::default();
640        let result = run_topic_check("test_lfs_missing_size_topic", LFS_MISSING_SIZE, check);
641        test_result_errors(
642            result,
643            &["an LFS pointer is missing the `size` key in `missing-size.lfs`."],
644        );
645    }
646
647    #[test]
648    fn test_lfs_missing_size_topic_fixed() {
649        let check = LfsPointer::default();
650        run_topic_check_ok(
651            "test_lfs_missing_size_topic_fixed",
652            LFS_MISSING_SIZE_FIXED,
653            check,
654        )
655    }
656
657    #[test]
658    fn test_lfs_missing_oid() {
659        let check = LfsPointer::default();
660        let result = run_check("test_lfs_missing_oid", LFS_MISSING_OID, check);
661        test_result_errors(
662            result,
663            &[
664                "commit ceeabe41a44a4469b6644017af725b0127d92302 not allowed; an LFS pointer is \
665                 missing the `oid` key in `missing-oid.lfs`.",
666            ],
667        );
668    }
669
670    #[test]
671    fn test_lfs_missing_oid_topic() {
672        let check = LfsPointer::default();
673        let result = run_topic_check("test_lfs_missing_oid_topic", LFS_MISSING_OID, check);
674        test_result_errors(
675            result,
676            &["an LFS pointer is missing the `oid` key in `missing-oid.lfs`."],
677        );
678    }
679
680    #[test]
681    fn test_lfs_missing_oid_topic_fixed() {
682        let check = LfsPointer::default();
683        run_topic_check_ok(
684            "test_lfs_missing_oid_topic_fixed",
685            LFS_MISSING_OID_FIXED,
686            check,
687        )
688    }
689
690    #[test]
691    fn test_lfs_unrecognized_hash() {
692        let check = LfsPointer::default();
693        let result = run_check("test_lfs_unrecognized_hash", LFS_UNRECOGNIZED_HASH, check);
694        test_result_warnings(
695            result,
696            &[
697                "commit 1aa2873adebe911caf594c376efc5c522f8d4c3a contains an unrecognized hash \
698                 algorithm `md256` in LFS pointer `unrecognized-hash.lfs`.",
699            ],
700        );
701    }
702
703    #[test]
704    fn test_lfs_unrecognized_hash_topic() {
705        let check = LfsPointer::default();
706        let result = run_topic_check(
707            "test_lfs_unrecognized_hash_topic",
708            LFS_UNRECOGNIZED_HASH,
709            check,
710        );
711        test_result_warnings(
712            result,
713            &["unrecognized hash algorithm `md256` in LFS pointer `unrecognized-hash.lfs`."],
714        );
715    }
716
717    #[test]
718    fn test_lfs_unrecognized_hash_topic_fixed() {
719        let check = LfsPointer::default();
720        run_topic_check_ok(
721            "test_lfs_unrecognized_hash_topic_fixed",
722            LFS_UNRECOGNIZED_HASH_FIXED,
723            check,
724        )
725    }
726
727    #[test]
728    fn test_lfs_missing_hash() {
729        let check = LfsPointer::default();
730        let result = run_check("test_lfs_missing_hash", LFS_MISSING_HASH, check);
731        test_result_errors(
732            result,
733            &[
734                "commit 109806f98fd036cba88d07f94df162aad5086d0b not allowed; missing digest for \
735                 cdbd0acd629e920c87bb4d4ff1f5e85ca9393b61629ef7b2bd64b1a7e1fe4b96 algorithm in \
736                 LFS pointer `missing-hash.lfs`.",
737            ],
738        );
739    }
740
741    #[test]
742    fn test_lfs_missing_hash_topic() {
743        let check = LfsPointer::default();
744        let result = run_topic_check("test_lfs_missing_hash_topic", LFS_MISSING_HASH, check);
745        test_result_errors(result, &[
746            "missing digest for cdbd0acd629e920c87bb4d4ff1f5e85ca9393b61629ef7b2bd64b1a7e1fe4b96 \
747             algorithm in LFS pointer `missing-hash.lfs`.",
748        ]);
749    }
750
751    #[test]
752    fn test_lfs_missing_hash_topic_fixed() {
753        let check = LfsPointer::default();
754        run_topic_check_ok(
755            "test_lfs_missing_hash_topic_fixed",
756            LFS_MISSING_HASH_FIXED,
757            check,
758        )
759    }
760
761    #[test]
762    fn test_lfs_unsorted_keys() {
763        let check = LfsPointer::default();
764        let result = run_check("test_lfs_unsorted_keys", LFS_UNSORTED_KEYS, check);
765        test_result_errors(
766            result,
767            &[
768                "commit aaeedde2f188005c24e090bc44378b17770e3b94 not allowed; unsorted key `oid` \
769                 found in LFS pointer `unsorted-keys.lfs`.",
770            ],
771        );
772    }
773
774    #[test]
775    fn test_lfs_unsorted_keys_topic() {
776        let check = LfsPointer::default();
777        let result = run_topic_check("test_lfs_unsorted_keys_topic", LFS_UNSORTED_KEYS, check);
778        test_result_errors(
779            result,
780            &["unsorted key `oid` found in LFS pointer `unsorted-keys.lfs`."],
781        );
782    }
783
784    #[test]
785    fn test_lfs_unsorted_keys_topic_fixed() {
786        let check = LfsPointer::default();
787        run_topic_check_ok(
788            "test_lfs_unsorted_keys_topic_fixed",
789            LFS_UNSORTED_KEYS_FIXED,
790            check,
791        )
792    }
793
794    #[test]
795    fn test_lfs_empty_pointer() {
796        let check = LfsPointer::default();
797        run_check_ok("test_lfs_empty_pointer", LFS_EMPTY_POINTER, check);
798    }
799
800    #[test]
801    fn test_lfs_empty_pointer_topic() {
802        let check = LfsPointer::default();
803        run_topic_check_ok("test_lfs_empty_pointer_topic", LFS_EMPTY_POINTER, check)
804    }
805
806    #[test]
807    fn test_lfs_valid_pointer() {
808        let check = LfsPointer::default();
809        run_check_ok("test_lfs_valid_pointer", LFS_VALID_POINTER, check);
810    }
811
812    #[test]
813    fn test_lfs_valid_pointer_topic() {
814        let check = LfsPointer::default();
815        run_topic_check_ok("test_lfs_valid_pointer_topic", LFS_VALID_POINTER, check)
816    }
817
818    #[test]
819    fn test_lfs_sha256_bad_digest_len() {
820        let check = LfsPointer::default();
821        let result = run_check(
822            "test_lfs_sha256_bad_digest_len",
823            LFS_SHA256_BAD_DIGEST_LEN,
824            check,
825        );
826        test_result_errors(
827            result,
828            &[
829                "commit b00442c09d00aeb1ffabffea821e8b286da6b0df not allowed; invalid digest \
830                 length in `deadbeefdeadbeefdeadbeefdeadbeef` for sha256 algorithm in LFS pointer \
831                 `sha256-digest-len-mismatch.lfs`.",
832            ],
833        );
834    }
835
836    #[test]
837    fn test_lfs_sha256_bad_digest_len_topic() {
838        let check = LfsPointer::default();
839        let result = run_topic_check(
840            "test_lfs_sha256_bad_digest_len_topic",
841            LFS_SHA256_BAD_DIGEST_LEN,
842            check,
843        );
844        test_result_errors(
845            result,
846            &[
847                "invalid digest length in `deadbeefdeadbeefdeadbeefdeadbeef` for sha256 algorithm \
848                 in LFS pointer `sha256-digest-len-mismatch.lfs`.",
849            ],
850        );
851    }
852
853    #[test]
854    fn test_lfs_sha256_bad_digest_len_topic_fixed() {
855        let check = LfsPointer::default();
856        run_topic_check_ok(
857            "test_lfs_sha256_bad_digest_len_topic_fixed",
858            LFS_SHA256_BAD_DIGEST_LEN_FIXED,
859            check,
860        )
861    }
862
863    #[test]
864    fn test_lfs_sha256_bad_digest_content() {
865        let check = LfsPointer::default();
866        let result = run_check(
867            "test_lfs_sha256_bad_digest_content",
868            LFS_SHA256_BAD_DIGEST_CONTENT,
869            check,
870        );
871        test_result_errors(
872            result,
873            &[
874                "commit 1cd551597d65f74a57928c024ff58edb532767e0 not allowed; invalid digest \
875                 content `deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefasciihex` for \
876                 sha256 algorithm in LFS pointer `sha256-digest-content.lfs`.",
877            ],
878        );
879    }
880
881    #[test]
882    fn test_lfs_sha256_bad_digest_content_topic() {
883        let check = LfsPointer::default();
884        let result = run_topic_check(
885            "test_lfs_sha256_bad_digest_content_topic",
886            LFS_SHA256_BAD_DIGEST_CONTENT,
887            check,
888        );
889        test_result_errors(
890            result,
891            &["invalid digest content \
892               `deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefasciihex` for \
893               sha256 algorithm in LFS pointer `sha256-digest-content.lfs`."],
894        );
895    }
896
897    #[test]
898    fn test_lfs_sha256_bad_digest_content_topic_fixed() {
899        let check = LfsPointer::default();
900        run_topic_check_ok(
901            "test_lfs_sha256_bad_digest_content_topic_fixed",
902            LFS_SHA256_BAD_DIGEST_CONTENT_FIXED,
903            check,
904        )
905    }
906
907    #[test]
908    fn test_lfs_sha256_empty_digest() {
909        let check = LfsPointer::default();
910        let result = run_check(
911            "test_lfs_sha256_empty_digest",
912            LFS_SHA256_EMPTY_DIGEST,
913            check,
914        );
915        test_result_errors(
916            result,
917            &[
918                "commit 6d377c8197b372df64689cb90db70904a2667dc9 not allowed; empty digest for \
919                 sha256 algorithm in LFS pointer `sha256-digest-empty.lfs`.",
920            ],
921        );
922    }
923
924    #[test]
925    fn test_lfs_sha256_empty_digest_topic() {
926        let check = LfsPointer::default();
927        let result = run_topic_check(
928            "test_lfs_sha256_empty_digest_topic",
929            LFS_SHA256_EMPTY_DIGEST,
930            check,
931        );
932        test_result_errors(
933            result,
934            &["empty digest for sha256 algorithm in LFS pointer `sha256-digest-empty.lfs`."],
935        );
936    }
937
938    #[test]
939    fn test_lfs_sha256_empty_digest_topic_fixed() {
940        let check = LfsPointer::default();
941        run_topic_check_ok(
942            "test_lfs_sha256_empty_digest_topic_fixed",
943            LFS_SHA256_EMPTY_DIGEST_FIXED,
944            check,
945        )
946    }
947
948    #[test]
949    fn test_lfs_unknown_algo_empty_digest() {
950        let check = LfsPointer::default();
951        let result = run_check(
952            "test_lfs_unknown_algo_empty_digest",
953            LFS_UNKNOWN_ALGO_EMPTY_DIGEST,
954            check,
955        );
956
957        assert_eq!(result.warnings().len(), 1);
958        assert_eq!(
959            result.warnings()[0],
960            "commit 96c193edaf748309b7bb5c2e816abda4a06c80c0 contains an unrecognized hash \
961             algorithm `notanalgo` in LFS pointer `unknown-digest-empty.lfs`.",
962        );
963        assert_eq!(result.alerts().len(), 0);
964        assert_eq!(result.errors().len(), 1);
965        assert_eq!(
966            result.errors()[0],
967            "commit 96c193edaf748309b7bb5c2e816abda4a06c80c0 not allowed; empty digest for \
968             notanalgo algorithm in LFS pointer `unknown-digest-empty.lfs`.",
969        );
970        assert!(!result.temporary());
971        assert!(!result.allowed());
972        assert!(!result.pass());
973    }
974
975    #[test]
976    fn test_lfs_unknown_algo_empty_digest_topic() {
977        let check = LfsPointer::default();
978        let result = run_topic_check(
979            "test_lfs_unknown_algo_empty_digest_topic",
980            LFS_UNKNOWN_ALGO_EMPTY_DIGEST,
981            check,
982        );
983
984        assert_eq!(result.warnings().len(), 1);
985        assert_eq!(
986            result.warnings()[0],
987            "unrecognized hash algorithm `notanalgo` in LFS pointer `unknown-digest-empty.lfs`.",
988        );
989        assert_eq!(result.alerts().len(), 0);
990        assert_eq!(result.errors().len(), 1);
991        assert_eq!(
992            result.errors()[0],
993            "empty digest for notanalgo algorithm in LFS pointer `unknown-digest-empty.lfs`.",
994        );
995        assert!(!result.temporary());
996        assert!(!result.allowed());
997        assert!(!result.pass());
998    }
999
1000    #[test]
1001    fn test_lfs_unknown_algo_empty_digest_topic_fixed() {
1002        let check = LfsPointer::default();
1003        let result = run_topic_check(
1004            "test_lfs_unknown_algo_empty_digest_topic_fixed",
1005            LFS_UNKNOWN_ALGO_EMPTY_DIGEST_FIXED,
1006            check,
1007        );
1008
1009        test_result_warnings(
1010            result,
1011            &["unrecognized hash algorithm `notanalgo` in LFS pointer \
1012               `unknown-digest-empty.lfs`."],
1013        );
1014    }
1015
1016    #[test]
1017    fn test_lfs_missing_algo() {
1018        let check = LfsPointer::default();
1019        let result = run_check("test_lfs_missing_algo", LFS_MISSING_ALGO, check);
1020        test_result_errors(
1021            result,
1022            &[
1023                "commit 6b4125e802283d716ceef0f4c7630a94810654fd not allowed; missing hash \
1024                 algorithm in LFS pointer `missing-oid-algo.lfs`.",
1025            ],
1026        );
1027    }
1028
1029    #[test]
1030    fn test_lfs_missing_algo_topic() {
1031        let check = LfsPointer::default();
1032        let result = run_topic_check("test_lfs_missing_algo_topic", LFS_MISSING_ALGO, check);
1033        test_result_errors(
1034            result,
1035            &["missing hash algorithm in LFS pointer `missing-oid-algo.lfs`."],
1036        );
1037    }
1038
1039    #[test]
1040    fn test_lfs_missing_algo_topic_fixed() {
1041        let check = LfsPointer::default();
1042        run_topic_check_ok(
1043            "test_lfs_missing_algo_topic_fixed",
1044            LFS_MISSING_ALGO_FIXED,
1045            check,
1046        )
1047    }
1048
1049    #[test]
1050    fn test_lfs_invalid_key() {
1051        let check = LfsPointer::default();
1052        let result = run_check("test_lfs_invalid_key", LFS_INVALID_KEY, check);
1053        test_result_errors(
1054            result,
1055            &[
1056                "commit 335b860660b133c59c651b3cb22ac883fdb8d004 not allowed; invalid line in an \
1057                 LFS pointer added in `invalid-key.lfs` on line 2.",
1058            ],
1059        );
1060    }
1061
1062    #[test]
1063    fn test_lfs_invalid_key_topic() {
1064        let check = LfsPointer::default();
1065        let result = run_topic_check("test_lfs_invalid_key_topic", LFS_INVALID_KEY, check);
1066        test_result_errors(
1067            result,
1068            &["invalid line in an LFS pointer added in `invalid-key.lfs` on line 2."],
1069        );
1070    }
1071
1072    #[test]
1073    fn test_lfs_invalid_key_topic_fixed() {
1074        let check = LfsPointer::default();
1075        run_topic_check_ok(
1076            "test_lfs_invalid_key_topic_fixed",
1077            LFS_INVALID_KEY_FIXED,
1078            check,
1079        )
1080    }
1081
1082    #[test]
1083    fn test_lfs_empty_key() {
1084        let check = LfsPointer::default();
1085        let result = run_check("test_lfs_empty_key", LFS_EMPTY_KEY, check);
1086        test_result_ok(result);
1087    }
1088
1089    #[test]
1090    fn test_lfs_empty_key_topic() {
1091        let check = LfsPointer::default();
1092        let result = run_topic_check("test_lfs_empty_key_topic", LFS_EMPTY_KEY, check);
1093        test_result_ok(result);
1094    }
1095
1096    #[test]
1097    fn test_lfs_empty_key_topic_fixed() {
1098        let check = LfsPointer::default();
1099        run_topic_check_ok("test_lfs_empty_key_topic_fixed", LFS_EMPTY_KEY_FIXED, check)
1100    }
1101
1102    #[test]
1103    fn test_lfs_delete_file() {
1104        let check = LfsPointer::default();
1105        let conf = make_check_conf(&check);
1106
1107        let result = test_check_base(
1108            "test_lfs_delete_file",
1109            LFS_DELETED_BAD_FILE,
1110            LFS_INVALID_UTF8,
1111            &conf,
1112        );
1113        test_result_ok(result);
1114    }
1115
1116    #[test]
1117    fn test_lfs_delete_file_topic() {
1118        let check = LfsPointer::default();
1119        let result = run_topic_check("test_lfs_delete_file_topic", LFS_DELETED_BAD_FILE, check);
1120        test_result_ok(result);
1121    }
1122
1123    #[test]
1124    fn test_lfs_invalid_size() {
1125        let check = LfsPointer::default();
1126        let result = run_check("test_lfs_invalid_size", LFS_NONINT_SIZE, check);
1127        test_result_errors(result, &[
1128            "commit 3ed812f83b9bd5d8ed5538933254d07a42928e47 not allowed; the `size` key value in \
1129             LFS pointer `invalid-size.lfs` must be an unsigned integer; found `notanint`.",
1130        ]);
1131    }
1132
1133    #[test]
1134    fn test_lfs_invalid_size_topic() {
1135        let check = LfsPointer::default();
1136        let result = run_topic_check("test_lfs_invalid_size_topic", LFS_NONINT_SIZE, check);
1137        test_result_errors(result, &[
1138            "the `size` key value in LFS pointer `invalid-size.lfs` must be an unsigned integer; \
1139             found `notanint`.",
1140        ]);
1141    }
1142
1143    #[test]
1144    fn test_lfs_invalid_size_topic_fixed() {
1145        let check = LfsPointer::default();
1146        run_topic_check_ok(
1147            "test_lfs_invalid_size_topic_fixed",
1148            LFS_NONINT_SIZE_FIXED,
1149            check,
1150        )
1151    }
1152
1153    #[test]
1154    fn test_lfs_zero_size() {
1155        let check = LfsPointer::default();
1156        let result = run_check("test_lfs_zero_size", LFS_ZERO_SIZE, check);
1157        test_result_errors(
1158            result,
1159            &[
1160                "commit 5523b49074a66611aaafcac32cf7d05a654f552c not allowed; the `size` value in \
1161                 LFS pointer `zero-size.lfs` must be greater than 0; found `0`.",
1162            ],
1163        );
1164    }
1165
1166    #[test]
1167    fn test_lfs_zero_size_topic() {
1168        let check = LfsPointer::default();
1169        let result = run_topic_check("test_lfs_zero_size_topic", LFS_ZERO_SIZE, check);
1170        test_result_errors(result, &[
1171            "the `size` value in LFS pointer `zero-size.lfs` must be greater than 0; found `0`.",
1172        ]);
1173    }
1174
1175    #[test]
1176    fn test_lfs_zero_size_topic_fixed() {
1177        let check = LfsPointer::default();
1178        run_topic_check_ok("test_lfs_zero_size_topic_fixed", LFS_ZERO_SIZE_FIXED, check)
1179    }
1180
1181    #[test]
1182    fn test_lfs_not_lfs_filter() {
1183        let check = LfsPointer::default();
1184        run_check_ok("test_lfs_not_lfs_filter", LFS_NOT_LFS_FILTER, check);
1185    }
1186
1187    #[test]
1188    fn test_lfs_not_lfs_filter_topic() {
1189        let check = LfsPointer::default();
1190        run_topic_check_ok("test_lfs_not_lfs_filter_topic", LFS_NOT_LFS_FILTER, check);
1191    }
1192}