Skip to main content

crates_readme_table/
lib.rs

1//! Crates README table: fetches user's crates from crates.io and updates README.
2
3mod api;
4mod readme;
5mod table;
6
7pub use api::{CRATES_IO_USER_AGENT, Crate, CratesResponse, Meta, get_all_crates, get_crates};
8pub use readme::update_readme_with_table;
9pub use table::build_crates_table;
10
11#[cfg(test)]
12mod tests {
13    use super::*;
14    use crate::api::Crate;
15    use crate::readme::{SECTION_END, SECTION_START, update_readme_with_table_internal};
16    use crate::table::base_url;
17    use std::fs;
18    use std::io::Write;
19    use tempfile::NamedTempFile;
20
21    #[tokio::test]
22    async fn test_get_crates() {
23        let client = reqwest::Client::builder()
24            .user_agent(CRATES_IO_USER_AGENT)
25            .build()
26            .unwrap();
27        let crates = get_crates(&client, 1, 10, "alpha", 335369).await.unwrap();
28        println!("{:?}", crates);
29    }
30
31    #[tokio::test]
32    async fn test_get_all_crates() {
33        let client = reqwest::Client::builder()
34            .user_agent(CRATES_IO_USER_AGENT)
35            .build()
36            .unwrap();
37        let crates = get_all_crates(&client, 335369).await.unwrap();
38        assert!(!crates.is_empty());
39    }
40
41    #[test]
42    fn test_base_url_prefers_homepage_then_repository_then_crates_io() {
43        let c_with_homepage = Crate {
44            id: "id1".into(),
45            name: "crate1".into(),
46            description: None,
47            homepage: Some("https://example.com/home".into()),
48            repository: Some("https://github.com/owner/repo.git".into()),
49            links: None,
50        };
51        assert_eq!(
52            base_url(&c_with_homepage),
53            "https://example.com/home".to_string()
54        );
55
56        let c_with_repo_only = Crate {
57            id: "id1".into(),
58            name: "crate1".into(),
59            description: None,
60            homepage: None,
61            repository: Some("https://github.com/owner/repo.git".into()),
62            links: None,
63        };
64        assert_eq!(
65            base_url(&c_with_repo_only),
66            "https://github.com/owner/repo".to_string()
67        );
68
69        let c_fallback = Crate {
70            id: "id1".into(),
71            name: "crate1".into(),
72            description: None,
73            homepage: None,
74            repository: None,
75            links: None,
76        };
77        assert_eq!(
78            base_url(&c_fallback),
79            "https://crates.io/crates/crate1".to_string()
80        );
81    }
82
83    #[test]
84    fn test_build_crates_table_produces_expected_markdown() {
85        let c = Crate {
86            id: "id1".into(),
87            name: "crate1".into(),
88            description: Some("Line1\nLine2".into()),
89            homepage: Some("https://github.com/owner/repo".into()),
90            repository: None,
91            links: None,
92        };
93        let table = build_crates_table(&[c]);
94        assert!(table.contains("|package|introduction|total downloads|"));
95        assert!(table.contains("@crate1"));
96        assert!(table.contains("Line1 Line2"));
97        assert!(table.contains("https://img.shields.io/crates/d/crate1"));
98    }
99
100    #[test]
101    fn test_update_readme_with_table_replaces_existing_section() {
102        let mut tmp = NamedTempFile::new().unwrap();
103        writeln!(
104            tmp,
105            "Header\n{}\n\nold table\n\n{}",
106            SECTION_START, SECTION_END
107        )
108        .unwrap();
109
110        let path = tmp.path().to_path_buf();
111        let table = "|header|";
112        update_readme_with_table_internal(&path, table).unwrap();
113
114        let contents = fs::read_to_string(&path).unwrap();
115        assert!(contents.contains(table));
116        assert!(contents.contains(SECTION_START));
117        assert!(contents.contains(SECTION_END));
118        assert!(!contents.contains("old table"));
119    }
120
121    #[test]
122    fn test_update_readme_with_table_appends_section_when_missing() {
123        let mut tmp = NamedTempFile::new().unwrap();
124        writeln!(tmp, "Some content").unwrap();
125
126        let path = tmp.path().to_path_buf();
127        let table = "|header|";
128        update_readme_with_table_internal(&path, table).unwrap();
129
130        let contents = fs::read_to_string(&path).unwrap();
131        assert!(contents.contains("Some content"));
132        assert!(contents.contains(SECTION_START));
133        assert!(contents.contains(SECTION_END));
134        assert!(contents.contains(table));
135    }
136}