443e322c

Archive
Tree [443e322c] [>]
commit
443e322c83489d94772f38e2e7c79b8402f657ff
parent
author
Christopher K. Schmitt <me@shmish.dev>
date
2026-07-29
committer
Christopher K. Schmitt <me@shmish.dev>
date
2026-07-29
changes
9
insertions
574
deletions
0
Add unit tests for all pure functions
Msrc/filters.rs
~        plural.to_string()~    })~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn pluralize_singular() {+        assert_eq!(pluralize(&1, "item", "items").unwrap(), "item");+    }++    #[test]+    fn pluralize_zero() {+        assert_eq!(pluralize(&0, "item", "items").unwrap(), "items");+    }++    #[test]+    fn pluralize_multiple() {+        assert_eq!(pluralize(&3, "item", "items").unwrap(), "items");+    }+}
Msrc/git.rs
~~    Ok(entries)~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn validate_path_normal() {+        assert!(validate_path("foo").is_ok());+    }++    #[test]+    fn validate_path_nested() {+        assert!(validate_path("foo/bar").is_ok());+    }++    #[test]+    fn validate_path_empty() {+        assert!(validate_path("").is_ok());+    }++    #[test]+    fn validate_path_dotdot() {+        assert!(validate_path("..").is_err());+    }++    #[test]+    fn validate_path_dotdot_prefix() {+        assert!(validate_path("../foo").is_err());+    }++    #[test]+    fn validate_path_nested_dotdot() {+        assert!(validate_path("foo/../../bar").is_err());+    }++    #[test]+    fn validate_path_absolute() {+        assert!(validate_path("/absolute").is_err());+    }++    #[test]+    fn corrupt_ok() {+        let result: std::result::Result<i32, &str> = Ok(42);+        assert_eq!(result.corrupt().unwrap(), 42);+    }++    #[test]+    fn corrupt_err() {+        let result: std::result::Result<i32, &str> = Err("boom");+        let err = result.corrupt().unwrap_err();+        assert!(matches!(err, crate::error::Error::GitCorrupt(_)));+    }++    #[test]+    fn flatten_refs_skips_outer_err() {+        let result: std::result::Result<Vec<std::result::Result<i32, ()>>, ()> = Err(());+        let collected: Vec<i32> = flatten_refs(result).collect();+        assert!(collected.is_empty());+    }++    #[test]+    fn flatten_refs_skips_inner_err() {+        let result: std::result::Result<Vec<std::result::Result<i32, &str>>, ()> =+            Ok(vec![Ok(1), Err("skip"), Ok(3)]);+        let collected: Vec<i32> = flatten_refs(result).collect();+        assert_eq!(collected, vec![1, 3]);+    }++    #[test]+    fn commit_color_deterministic() {+        let c1 = commit_color("abc123");+        let c2 = commit_color("abc123");+        assert_eq!(c1, c2);+    }++    #[test]+    fn commit_color_in_palette() {+        let c = commit_color("xyz789");+        assert!(COLORS.contains(&c), "color {c} not in palette");+    }+}
Msrc/routes.rs
~        .route("/{repo}/{*path}", get(fallback))~        .with_state(state)~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn breadcrumbs_empty_path() {+        let crumbs = build_breadcrumbs("", "repo", "abc123", "");+        assert_eq!(crumbs.len(), 1);+        assert_eq!(crumbs[0].label, "tree");+        assert_eq!(crumbs[0].url, "/repo/commits/abc123/tree");+    }++    #[test]+    fn breadcrumbs_single_segment() {+        let crumbs = build_breadcrumbs("", "repo", "abc123", "src");+        assert_eq!(crumbs.len(), 2);+        assert_eq!(crumbs[0].label, "tree");+        assert_eq!(crumbs[1].label, "src");+        assert_eq!(crumbs[1].url, "/repo/commits/abc123/tree/src");+    }++    #[test]+    fn breadcrumbs_multiple_segments() {+        let crumbs = build_breadcrumbs("", "myrepo", "def456", "a/b/c");+        assert_eq!(crumbs.len(), 4);+        assert_eq!(crumbs[1].label, "a");+        assert_eq!(crumbs[1].url, "/myrepo/commits/def456/tree/a");+        assert_eq!(crumbs[2].url, "/myrepo/commits/def456/tree/a/b");+        assert_eq!(crumbs[3].url, "/myrepo/commits/def456/tree/a/b/c");+    }++    #[test]+    fn breadcrumbs_with_base_url() {+        let crumbs = build_breadcrumbs("/git", "repo", "abc", "src/lib");+        assert_eq!(crumbs[0].url, "/git/repo/commits/abc/tree");+        assert_eq!(crumbs[2].url, "/git/repo/commits/abc/tree/src/lib");+    }+}
Msrc/routes/archive.rs
~        body,~    ))~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn config_tar_gz() {+        let cfg = archive_config("tar.gz").unwrap();+        assert_eq!(cfg.content_type, "application/gzip");+        assert_eq!(cfg.extension, "tar.gz");+        assert!(!cfg.uses_xz);+    }++    #[test]+    fn config_tgz() {+        let cfg = archive_config("tgz").unwrap();+        assert_eq!(cfg.content_type, "application/gzip");+        assert!(!cfg.uses_xz);+    }++    #[test]+    fn config_tar_xz() {+        let cfg = archive_config("tar.xz").unwrap();+        assert_eq!(cfg.content_type, "application/x-xz");+        assert!(cfg.uses_xz);+    }++    #[test]+    fn config_txz() {+        let cfg = archive_config("txz").unwrap();+        assert_eq!(cfg.content_type, "application/x-xz");+        assert!(cfg.uses_xz);+    }++    #[test]+    fn config_zip() {+        let cfg = archive_config("zip").unwrap();+        assert_eq!(cfg.content_type, "application/zip");+        assert!(!cfg.uses_xz);+    }++    #[test]+    fn config_unsupported() {+        assert!(archive_config("rar").is_none());+        assert!(archive_config("tar").is_none());+        assert!(archive_config("").is_none());+    }++    #[test]+    fn sanitize_passes_clean() {+        assert_eq!(sanitize_filename_component("hello"), "hello");+    }++    #[test]+    fn sanitize_replaces_double_quote() {+        assert_eq!(sanitize_filename_component(r#"foo"bar"#), "foo-bar");+    }++    #[test]+    fn sanitize_replaces_backslash() {+        assert_eq!(sanitize_filename_component(r"a\b"), "a-b");+    }+}
Msrc/routes/commit.rs
~        site: state.site,~    })~}++#[cfg(test)]+mod tests {+    use super::*;++    fn commit(sha: &str, parents: &[&str]) -> git::CommitEntry {+        git::CommitEntry {+            sha: sha.to_string(),+            message: String::new(),+            date: String::new(),+            parents: parents.iter().map(|s| s.to_string()).collect(),+        }+    }++    // --- compute_layout ---++    #[test]+    fn layout_linear_chain() {+        let commits = vec![+            commit("C", &["B"]),+            commit("B", &["A"]),+            commit("A", &[]),+        ];+        let layout = compute_layout(&commits, 0);+        assert_eq!(layout.positions.len(), 3);+        assert_eq!(layout.positions.get("C"), Some(&(0, 0)));+        assert_eq!(layout.positions.get("B"), Some(&(1, 0)));+        assert_eq!(layout.positions.get("A"), Some(&(2, 0)));+    }++    #[test]+    fn layout_diamond_merge() {+        let commits = vec![+            commit("D", &["B", "C"]),+            commit("C", &["A"]),+            commit("B", &["A"]),+            commit("A", &[]),+        ];+        let layout = compute_layout(&commits, 0);+        assert_eq!(layout.positions.get("D"), Some(&(0, 0)));+        assert_eq!(layout.positions.get("C"), Some(&(1, 1)));+        assert_eq!(layout.positions.get("B"), Some(&(2, 0)));+        assert_eq!(layout.positions.get("A"), Some(&(3, 1)));+    }++    #[test]+    fn layout_root_commit() {+        let commits = vec![commit("A", &[])];+        let layout = compute_layout(&commits, 0);+        assert_eq!(layout.positions.get("A"), Some(&(0, 0)));+        assert!(layout.active_at_page_start.is_empty());+    }++    #[test]+    fn layout_octopus_merge() {+        let commits = vec![+            commit("E", &["B", "C", "D"]),+            commit("D", &["A"]),+            commit("C", &["A"]),+            commit("B", &["A"]),+            commit("A", &[]),+        ];+        let layout = compute_layout(&commits, 0);+        assert_eq!(layout.positions.get("E"), Some(&(0, 0)));+        assert_eq!(layout.positions.get("D"), Some(&(1, 2)));+        assert_eq!(layout.positions.get("C"), Some(&(2, 1)));+        assert_eq!(layout.positions.get("B"), Some(&(3, 0)));+        assert_eq!(layout.positions.get("A"), Some(&(4, 2)));+    }++    #[test]+    fn layout_empty_input() {+        let layout = compute_layout(&[], 0);+        assert!(layout.positions.is_empty());+        assert!(layout.active_at_page_start.is_empty());+    }++    #[test]+    fn layout_page_start_captures_active() {+        let commits = vec![+            commit("D", &["C"]),+            commit("C", &["B"]),+            commit("B", &["A"]),+            commit("A", &[]),+        ];+        let layout = compute_layout(&commits, 1);+        assert_eq!(layout.active_at_page_start.len(), 1);+        assert_eq!(layout.active_at_page_start[0], Some("C".to_string()));+    }++    #[test]+    fn layout_lane_steal_then_free() {+        let commits = vec![+            commit("C", &["B"]),+            commit("B", &["A"]),+            commit("A", &[]),+        ];+        let layout = compute_layout(&commits, 0);+        // C steals lane 0, B inherits it, A frees it+        assert_eq!(layout.positions.get("C"), Some(&(0, 0)));+        assert_eq!(layout.positions.get("B"), Some(&(1, 0)));+        assert_eq!(layout.positions.get("A"), Some(&(2, 0)));+    }++    // --- render_page_svg ---++    #[test]+    fn svg_empty_range() {+        let layout = compute_layout(&[], 0);+        assert_eq!(render_page_svg(&[], &layout, 0, 0), "");+    }++    #[test]+    fn svg_single_commit() {+        let commits = vec![commit("A", &[])];+        let layout = compute_layout(&commits, 0);+        let svg = render_page_svg(&commits, &layout, 0, 1);+        assert!(svg.starts_with(r#"<svg width="#), "expected SVG start, got: {svg}");+        assert!(svg.contains("<circle"), "expected circle element");+        assert!(svg.ends_with("</svg>"));+    }++    #[test]+    fn svg_two_commits_same_lane() {+        let commits = vec![commit("B", &["A"]), commit("A", &[])];+        let layout = compute_layout(&commits, 0);+        let svg = render_page_svg(&commits, &layout, 0, 2);+        assert!(svg.contains("<line"), "expected line element");+        assert_eq!(svg.matches("<line").count(), 1);+        assert_eq!(svg.matches("<circle").count(), 2);+    }++    // --- DiffHunkCollector ---++    #[test]+    fn diff_hunk_context_only() {+        let mut hunks = Vec::new();+        let mut added = 0;+        let mut removed = 0;+        let mut collector = DiffHunkCollector {+            hunks: &mut hunks,+            file_added: &mut added,+            file_removed: &mut removed,+        };+        let header = gix::diff::blob::unified_diff::HunkHeader {+            before_hunk_start: 1,+            before_hunk_len: 1,+            after_hunk_start: 1,+            after_hunk_len: 1,+        };+        collector+            .consume_hunk(header, &[(DiffLineKind::Context, b"keep")])+            .unwrap();+        assert_eq!(hunks.len(), 1);+        assert_eq!(hunks[0].len(), 1);+        assert_eq!(hunks[0][0].marker, "~");+        assert_eq!(hunks[0][0].content, "keep");+        assert_eq!(added, 0);+        assert_eq!(removed, 0);+    }++    #[test]+    fn diff_hunk_mixed_lines() {+        let mut hunks = Vec::new();+        let mut added = 0;+        let mut removed = 0;+        let mut collector = DiffHunkCollector {+            hunks: &mut hunks,+            file_added: &mut added,+            file_removed: &mut removed,+        };+        let header = gix::diff::blob::unified_diff::HunkHeader {+            before_hunk_start: 1,+            before_hunk_len: 2,+            after_hunk_start: 1,+            after_hunk_len: 2,+        };+        collector+            .consume_hunk(+                header,+                &[+                    (DiffLineKind::Context, b"ctx"),+                    (DiffLineKind::Add, b"new"),+                    (DiffLineKind::Remove, b"old"),+                ],+            )+            .unwrap();+        assert_eq!(hunks.len(), 1);+        assert_eq!(hunks[0].len(), 3);+        assert_eq!(hunks[0][0].marker, "~");+        assert_eq!(hunks[0][1].marker, "+");+        assert_eq!(hunks[0][2].marker, "-");+        assert_eq!(added, 1);+        assert_eq!(removed, 1);+    }++    #[test]+    fn diff_hunk_binary_content() {+        let mut hunks = Vec::new();+        let mut added = 0;+        let mut removed = 0;+        let mut collector = DiffHunkCollector {+            hunks: &mut hunks,+            file_added: &mut added,+            file_removed: &mut removed,+        };+        let header = gix::diff::blob::unified_diff::HunkHeader {+            before_hunk_start: 1,+            before_hunk_len: 1,+            after_hunk_start: 1,+            after_hunk_len: 1,+        };+        collector+            .consume_hunk(header, &[(DiffLineKind::Add, b"\xff\xfe\x00\x01")])+            .unwrap();+        // Invalid UTF-8 should fall back to <binary>+        assert_eq!(hunks[0][0].content, "<binary>");+    }++    #[test]+    fn diff_hunk_empty_input() {+        let mut hunks = Vec::new();+        let mut added = 0;+        let mut removed = 0;+        let mut collector = DiffHunkCollector {+            hunks: &mut hunks,+            file_added: &mut added,+            file_removed: &mut removed,+        };+        let header = gix::diff::blob::unified_diff::HunkHeader {+            before_hunk_start: 0,+            before_hunk_len: 0,+            after_hunk_start: 0,+            after_hunk_len: 0,+        };+        collector.consume_hunk(header, &[]).unwrap();+        assert_eq!(hunks.len(), 1);+        assert!(hunks[0].is_empty());+    }+}
Msrc/routes/git_upload.rs
~        Body::from_stream(stream),~    ))~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn pkt_line_hello() {+        assert_eq!(pkt_line("hello"), b"0009hello");+    }++    #[test]+    fn pkt_line_empty() {+        assert_eq!(pkt_line(""), b"0004");+    }++    #[test]+    fn pkt_line_single_char() {+        assert_eq!(pkt_line("x"), b"0005x");+    }+}
Msrc/routes/raw.rs
~        _ => "application/octet-stream",~    }~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn content_type_png() {+        assert_eq!(content_type_for("image.png"), "image/png");+    }++    #[test]+    fn content_type_jpg() {+        assert_eq!(content_type_for("photo.jpg"), "image/jpeg");+    }++    #[test]+    fn content_type_jpeg() {+        assert_eq!(content_type_for("photo.jpeg"), "image/jpeg");+    }++    #[test]+    fn content_type_svg() {+        assert_eq!(content_type_for("graph.svg"), "image/svg+xml");+    }++    #[test]+    fn content_type_md() {+        assert_eq!(content_type_for("readme.md"), "text/plain; charset=utf-8");+    }++    #[test]+    fn content_type_unknown() {+        assert_eq!(content_type_for("file.xyz"), "application/octet-stream");+    }++    #[test]+    fn content_type_no_extension() {+        assert_eq!(content_type_for("Makefile"), "application/octet-stream");+    }+}
Msrc/routes/repo.rs
~~    Ok(RepoList { repos, site: state.site })~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn relative_http() {+        assert!(!is_relative_url("http://example.com"));+    }++    #[test]+    fn relative_https() {+        assert!(!is_relative_url("https://example.com"));+    }++    #[test]+    fn relative_http_case_sensitive() {+        // Known quirk: uppercase scheme not matched, treated as relative+        assert!(is_relative_url("Http://example.com"));+    }++    #[test]+    fn relative_hash() {+        assert!(!is_relative_url("#section"));+    }++    #[test]+    fn relative_mailto() {+        assert!(!is_relative_url("mailto:user@host"));+    }++    #[test]+    fn relative_data() {+        assert!(!is_relative_url("data:text/plain,hi"));+    }++    #[test]+    fn relative_path_relative() {+        assert!(is_relative_url("relative/path"));+    }++    #[test]+    fn relative_path_absolute() {+        assert!(is_relative_url("/absolute/path"));+    }+}
Msrc/routes/tree.rs
~        }~    }~}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn size_zero() {+        assert_eq!(format_size(0), "0 B");+    }++    #[test]+    fn size_bytes() {+        assert_eq!(format_size(1023), "1023 B");+    }++    #[test]+    fn size_kb() {+        assert_eq!(format_size(1024), "1.0 KB");+    }++    #[test]+    fn size_mb() {+        assert_eq!(format_size(1_048_576), "1.0 MB");+    }+}