kirei 25.12.29

Askama formatter
Documentation
use assert_cmd::{Command, cargo};
use assert_fs::{TempDir, prelude::*};
use predicates::str::contains;
use std::fs::write;

const ERR_PARSE_ASKAMA: &str = "failed to parse Askama";
const ERR_PARSE_HTML: &str = "failed to parse HTML";

#[test]
fn fails_when_unformatted() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("test.html");
    file.write_str("<div>  {% for item in items %}  {{ item }}  {% endfor %}  </div>")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg("--check")
        .arg(file.path())
        .assert()
        .failure()
        .code(1);
}

#[test]
fn prints_error_to_stderr() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("test.html");
    file.write_str("{% if user.logged_in %}<p>  Welcome {{ user.name }}!  </p>{% endif %}")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg("--check")
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("file would be formatted"));
}

#[test]
fn stdin_fails_when_unformatted() {
    Command::new(cargo::cargo_bin!("kirei"))
        .arg("-")
        .arg("--check")
        .write_stdin("{% match status %}{% when Status::Active %}<span>Active</span>{% endmatch %}")
        .assert()
        .failure()
        .code(1);
}

#[test]
fn list_different_shows_unformatted() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("test.html");
    file.write_str("<ul>{% for user in users %}  <li>{{ user }}</li>  {% endfor %}</ul>")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg("--list-different")
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("test.html"));
}

#[test]
fn list_different_with_stdin_shows_stdin() {
    Command::new(cargo::cargo_bin!("kirei"))
        .arg("-")
        .arg("--list-different")
        .write_stdin("{% if show %}<div>  <p>{{ content }}</p>  </div>{% endif %}")
        .assert()
        .failure()
        .code(1)
        .stderr(contains("missing file path"));
}

#[test]
fn stdin_filepath_with_list_different() {
    Command::new(cargo::cargo_bin!("kirei"))
        .arg("-")
        .arg("--list-different")
        .arg("--stdin-filepath")
        .arg("custom/path.html")
        .write_stdin("{% for i in items %}<div>  {{ i.name }}  </div>{% endfor %}")
        .assert()
        .failure()
        .code(1)
        .stderr(contains("custom/path.html"));
}

#[test]
fn fails_on_missing_file() {
    Command::new(cargo::cargo_bin!("kirei"))
        .arg("this_file_does_not_exist.html")
        .assert()
        .failure()
        .code(1)
        .stderr(contains(
            "error: file `this_file_does_not_exist.html` does not exist",
        ));
}

#[test]
fn fails_on_invalid_utf8() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("invalid.html");

    write(file.path(), [0xFF, 0xFE, 0xFD]).unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("error: invalid data"));
}

#[test]
fn fails_on_null_character() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("null_char.html");
    file.write_str("<div>\u{0000}</div>").unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .stderr(contains(ERR_PARSE_ASKAMA));
}

#[test]
fn fails_on_invalid_block() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("invalid_block.html");
    file.write_str("{% blop %}<p>A cool paragraph</p>").unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .stderr(contains(ERR_PARSE_ASKAMA));
}

#[test]
fn fails_on_invalid_nested_block() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("invalid_nested_block.html");
    file.write_str("<body>{% block first %}{% blop %}<p>A cool paragraph</p>{% endblock %}{% endblock %}</body>")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .stderr(contains(ERR_PARSE_ASKAMA));
}

#[test]
fn fails_on_mixed_unclosed_attr_quote() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("mixed_unclosed_attr_quote.html");
    file.write_str("<div class=\"{{ color }}>something</div>")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .stderr(contains(ERR_PARSE_HTML));
}

#[test]
fn fails_on_unclosed_attr_quote() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("unclosed_attr_quote.html");
    file.write_str(
        r#"<form id="login-form" class=user-form needs-validation" action="/login-submit" method="post" novalidate>
  <input type="email" id="user-email" class="form-control" name="email placeholder="Enter your email" required aria-label="Email Address">
</form>"#,
    )
    .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .stderr(contains(ERR_PARSE_HTML));
}

#[test]
fn fails_on_erroneous_end_tag_inline() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("mismatched_tag.html");
    file.write_str("<div><foo>Some text</bar></div>").unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("error: unexpected closing tag"))
        .stderr(contains("expected `foo`, found `bar`"))
        .stderr(contains("expected due to this open tag name"))
        .stderr(contains("help: consider using `foo`"));
}

#[test]
fn fails_on_erroneous_end_tag_multiline() {
    let temp = TempDir::new().unwrap();
    let file = temp.child("mismatched_tag.html");
    file.write_str("<div>\n    <foo>\n        Some text\n    </bar>\n</div>")
        .unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("error: unexpected closing tag"))
        .stderr(contains("expected `foo`, found `bar`"))
        .stderr(contains("expected due to this open tag name"))
        .stderr(contains("help: consider using `foo`"));
}

#[test]
#[cfg(unix)]
fn fails_on_permission_denied_read() {
    use std::os::unix::fs::PermissionsExt;

    let temp = TempDir::new().unwrap();
    let file = temp.child("no_read.html");
    file.write_str("<div>test</div>").unwrap();

    std::fs::set_permissions(file.path(), std::fs::Permissions::from_mode(0o000)).unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("error: permission denied when accessing"));
}

#[test]
#[cfg(unix)]
fn fails_on_permission_denied_write() {
    use std::os::unix::fs::PermissionsExt;

    let temp = TempDir::new().unwrap();
    let file = temp.child("no_write.html");
    file.write_str("<div>test</div>").unwrap();

    std::fs::set_permissions(file.path(), std::fs::Permissions::from_mode(0o444)).unwrap();

    Command::new(cargo::cargo_bin!("kirei"))
        .arg("--write")
        .arg(file.path())
        .assert()
        .failure()
        .code(1)
        .stderr(contains("error: permission denied when accessing"));
}