use shackles_git_hooks_adapter::{
    GitHookValueError, GitHooksAdapterRequirement, HookKind, ItemRequirements, ProductId,
    ScalarAssertion, TextFileContents,
};

#[test]
fn product_id_rejects_non_portable_components() {
    assert_eq!(
        ProductId::new(""),
        Err(GitHookValueError::EmptyProductId),
        "An empty product id must be rejected."
    );
    assert!(
        matches!(
            ProductId::new("/shakrs"),
            Err(GitHookValueError::AbsoluteProductId { .. })
        ),
        "An absolute product id must be rejected."
    );
    assert!(
        matches!(
            ProductId::new("a/b"),
            Err(GitHookValueError::PathSeparatorProductId { .. })
        ),
        "A product id with a separator must be rejected."
    );
    for value in [".", ".."] {
        assert!(
            matches!(
                ProductId::new(value),
                Err(GitHookValueError::ParentDirectoryProductId { .. })
            ),
            "A parent-directory component must be rejected."
        );
    }
    assert!(
        ProductId::new("shakrs..next").is_ok(),
        "Embedded dots are valid in one portable path component."
    );
}

#[test]
fn requirement_rejects_unsupported_assertion_shapes() -> Result<(), String> {
    let value = contents("main")?;
    let forbidden_main = ItemRequirements {
        required: Vec::new(),
        forbidden: vec![(value.clone(), "forbidden".to_owned())],
        closed: None,
    };
    assert!(
        matches!(
            requirement(
                forbidden_main,
                ScalarAssertion::Equals(value.clone(), "exact".to_owned())
            ),
            Err(GitHookValueError::ForbiddenMainContents)
        ),
        "Forbidden main contents must be rejected."
    );
    let closed_main = ItemRequirements {
        required: Vec::new(),
        forbidden: Vec::new(),
        closed: Some("closed".to_owned()),
    };
    assert!(
        matches!(
            requirement(
                closed_main,
                ScalarAssertion::Equals(value.clone(), "exact".to_owned())
            ),
            Err(GitHookValueError::ClosedMainContents)
        ),
        "Closed main contents must be rejected."
    );
    assert!(
        matches!(
            requirement(
                ItemRequirements::default(),
                ScalarAssertion::Present("present".to_owned())
            ),
            Err(GitHookValueError::FragmentContentsMustEqual)
        ),
        "A non-Equals fragment assertion must be rejected."
    );
    Ok(())
}

fn requirement(
    main: ItemRequirements<TextFileContents>,
    fragment: ScalarAssertion<TextFileContents>,
) -> Result<GitHooksAdapterRequirement, GitHookValueError> {
    GitHooksAdapterRequirement::new(
        HookKind::PreCommit,
        ProductId::new("shakrs")?,
        main,
        fragment,
    )
}

fn contents(value: &str) -> Result<TextFileContents, String> {
    TextFileContents::new(value.as_bytes().to_vec()).map_err(|err| err.to_string())
}
