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