~vikanezrimaya/kittybox

84f9508813ac368bdce302db7f3a15bc6fce3fee — Vika 2 months ago 6c1ebd1
templates: introduce unit tests

These unit tests generate a random MF2-JSON post, convert it to
MF2-HTML using the template and then read it back using the
`microformats` crate.

The only problem is that it has a nasty bug with overstuffing implied
properties. This is being worked on:
https://gitlab.com/maxburon/microformats-parser/-/issues/7

For now the tests marked as ignored because they fail. But the
function itself that generates them should remain here for
documentation and potential code sharing with the `microformats`
crate, potentially even migrating to a subcrate there.
3 files changed, 165 insertions(+), 0 deletions(-)

M Cargo.lock
M templates/Cargo.toml
M templates/src/lib.rs
M Cargo.lock => Cargo.lock +4 -0
@@ 1662,11 1662,15 @@ version = "0.1.0"
dependencies = [
 "chrono",
 "ellipse",
 "faker_rand",
 "http",
 "kittybox-util",
 "log 0.4.17",
 "markup",
 "microformats",
 "rand 0.8.5",
 "serde_json",
 "test-logger",
]

[[package]]

M templates/Cargo.toml => templates/Cargo.toml +7 -0
@@ 5,6 5,13 @@ edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dev-dependencies]
faker_rand = "^0.1.1"
rand = "^0.8.5"
test-logger = "^0.1.0"
[dev-dependencies.microformats]
version="^0.2.0"

[dependencies]
ellipse = "^0.2.0"           # Truncate and ellipsize strings in a human-friendly way
http = "^0.2.7"              # Hyper's strong HTTP types

M templates/src/lib.rs => templates/src/lib.rs +154 -0
@@ 4,3 4,157 @@ mod onboarding;
pub use onboarding::OnboardingPage;
mod login;
pub use login::LoginPage;

#[cfg(test)]
mod tests {
    use serde_json::json;
    use microformats::types::Document;

    enum PostType {
        Note,
        Article,
        ReplyTo(serde_json::Value),
        ReplyToLink(String),
        LikeOf(serde_json::Value),
        LikeOfLink(String)
    }

    fn gen_hcard(domain: &str) -> serde_json::Value {
        use faker_rand::en_us::names::FirstName;

        json!({
            "type": ["h-card"],
            "properties": {
                "name": [rand::random::<FirstName>().to_string()],
                "photo": [format!("https://{domain}/media/me.png")],
                "uid": [format!("https://{domain}/")],
                "url": [format!("https://{domain}/")]
            }
        })
    }
    
    fn gen_random_post(domain: &str, kind: PostType) -> serde_json::Value {
        use faker_rand::lorem::{Paragraph, Word, Sentence};

        fn html(content: Paragraph) -> serde_json::Value {
            json!({
                "html": format!("<p>{}</p>", content),
                "value": content.to_string()
            })
        }
            
        let uid = format!(
            "https://{domain}/posts/{}-{}-{}",
            rand::random::<Word>(), rand::random::<Word>(), rand::random::<Word>()
        );
        let dt = chrono::offset::Local::now()
            .to_rfc3339_opts(chrono::SecondsFormat::Secs, true);

        match kind {
            PostType::Note => {
                let content = rand::random::<Paragraph>();

                json!({
                    "type": ["h-entry"],
                    "properties": {
                        "content": [html(content)],
                        "published": [dt],
                        "uid": [&uid],
                        "url": [&uid],
                        "author": [gen_hcard(domain)]
                    }
                })
            }
            PostType::Article => {
                let content = rand::random::<Paragraph>();
                let name = rand::random::<Sentence>();

                json!({
                    "type": ["h-entry"],
                    "properties": {
                        "content": [html(content)],
                        "published": [dt],
                        "uid": [&uid],
                        "url": [&uid],
                        "author": [gen_hcard(domain)],
                        "name": [name.to_string()]
                    }
                })
            }
            _ => todo!()
        }
    }

    #[test]
    #[ignore = "see https://gitlab.com/maxburon/microformats-parser/-/issues/7"]
    fn test_note() {
        use microformats::types::PropertyValue;
        use faker_rand::en_us::internet::Domain;

        test_logger::ensure_env_logger_initialized();

        let mf2 = gen_random_post(
            &rand::random::<Domain>().to_string(),
            PostType::Note
        );

        let html = crate::templates::Entry {
            post: &mf2
        }.to_string();
        println!("\n```html\n{}\n```", &html);
        let url: microformats::types::Url = mf2["properties"]["uid"][0].as_str()
            .unwrap()
            .parse()
            .unwrap();
        let parsed: Document = microformats::from_html(&html, url.clone()).unwrap();

        let item = parsed.get_item_by_url(&url).unwrap();
        println!("\n```json\n{}\n```", serde_json::to_string_pretty(&item).unwrap());
        if let PropertyValue::Item(item) = item {
            let _item = item.borrow();
            let props = _item.properties.borrow();

            if let PropertyValue::Fragment(content) = props.get("content").and_then(|v| v.first()).unwrap() {
                assert_eq!(content.html, mf2["properties"]["content"][0]["html"].as_str().unwrap());
            } else {
                unreachable!()
            }

            assert!(props.contains_key("published"));
            use microformats::types::temporal::Value as TemporalValue;
            if let Some(PropertyValue::Temporal(
                TemporalValue::Timestamp(item)
            )) = props.get("published")
                .and_then(|v| v.first())
            {
                use chrono::{DateTime, FixedOffset, NaiveDateTime};

                let offset = item.as_offset().unwrap().data;
                let ndt: NaiveDateTime = item.as_date().unwrap().data
                    .and_time(item.as_time().unwrap().data)
                    - offset;
                let dt = DateTime::<FixedOffset>::from_utc(ndt, offset);

                let expected: DateTime<FixedOffset> = chrono::DateTime::parse_from_rfc3339(
                    mf2["properties"]["published"][0].as_str().unwrap()
                ).unwrap();

                assert_eq!(dt, expected);
            } else {
                panic!("Failed to find datetime in properties!");
            }
                
            assert!(props.contains_key("uid"));
            assert!(props.contains_key("url"));
            assert!(props.get("url")
                    .unwrap()
                    .iter()
                    .any(|i| i == props.get("uid").and_then(|v| v.first()).unwrap()));
            // XXX: fails because of https://gitlab.com/maxburon/microformats-parser/-/issues/7
            assert!(!props.contains_key("name"));

        } else {
            unreachable!()
        }
    }
}