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