// Integration tests for relicario-server org-hook path classification.

use relicario_server::{classify_path, PathClass};

#[test]
fn protected_files_are_classified_protected() {
    assert_eq!(classify_path("members.json"), PathClass::Protected);
    assert_eq!(classify_path("collections.json"), PathClass::Protected);
    assert_eq!(classify_path("org.json"), PathClass::Protected);
}

#[test]
fn item_write_yields_collection_slug() {
    assert_eq!(
        classify_path("items/prod/a1b2c3d4e5f6a1b2.enc"),
        PathClass::Item { collection: "prod".to_string() }
    );
}

#[test]
fn item_write_nested_slug_is_rejected() {
    // Slugs cannot contain '/', so a path with extra segments is malformed → Rejected.
    assert_eq!(
        classify_path("items/prod/sub/x.enc"),
        PathClass::Rejected("items path must be items/<slug>/<id>.enc".to_string())
    );
}

#[test]
fn key_blobs_and_manifest_are_unrestricted() {
    // keys/<id>.enc and manifest.enc are written by org operations; the SIGNATURE
    // check (every commit must be signed by a current member) is the gate for them.
    assert_eq!(classify_path("keys/a1b2c3d4e5f6a1b2.enc"), PathClass::Unrestricted);
    assert_eq!(classify_path("manifest.enc"), PathClass::Unrestricted);
}

#[test]
fn items_without_slug_segment_are_rejected() {
    // Flat items/<id>.enc (the OLD, now-removed layout) is no longer valid.
    assert_eq!(
        classify_path("items/a1b2c3d4e5f6a1b2.enc"),
        PathClass::Rejected("items path must be items/<slug>/<id>.enc".to_string())
    );
}

#[test]
fn empty_slug_segment_is_rejected() {
    assert_eq!(
        classify_path("items//x.enc"),
        PathClass::Rejected("empty collection slug in items path".to_string())
    );
}

#[test]
fn dotted_slug_is_rejected() {
    // Defense-in-depth (mirrors OrgCollections::validate): a slug containing '.'
    // — e.g. a ".."/"." path-traversal attempt — is rejected.
    assert_eq!(
        classify_path("items/../x.enc"),
        PathClass::Rejected("invalid collection slug: \"..\"".to_string())
    );
}

use relicario_server::extract_schema_version;

#[test]
fn extract_schema_version_reads_field() {
    let json = r#"{ "schema_version": 3, "members": [] }"#;
    assert_eq!(extract_schema_version(json).unwrap(), 3);
}

#[test]
fn extract_schema_version_errors_on_missing_field() {
    let json = r#"{ "members": [] }"#;
    assert!(extract_schema_version(json).is_err());
}

#[test]
fn extract_schema_version_errors_on_garbage() {
    assert!(extract_schema_version("not json").is_err());
}

#[test]
fn attachment_path_is_collection_scoped() {
    assert_eq!(
        classify_path("attachments/prod/a1b2c3d4e5f6a1b2/0011223344556677.enc"),
        PathClass::Item { collection: "prod".to_string() }
    );
}

#[test]
fn attachment_wrong_segment_count_is_rejected() {
    assert_eq!(
        classify_path("attachments/prod/onlytwo.enc"),
        PathClass::Rejected("attachments path must be attachments/<slug>/<item-id>/<att-id>.enc".to_string())
    );
}

#[test]
fn attachment_empty_or_dotted_slug_is_rejected() {
    assert!(matches!(classify_path("attachments//item/att.enc"), PathClass::Rejected(_)));
    assert!(matches!(classify_path("attachments/../item/att.enc"), PathClass::Rejected(_)));
}

#[test]
fn attachments_prefix_alone_is_rejected_not_unrestricted() {
    // `attachments/` with no slug/item/att segments must be Rejected, NOT fall
    // through to Unrestricted — that fall-through was the authz gap this closes.
    assert!(matches!(classify_path("attachments/"), PathClass::Rejected(_)));
}

#[test]
fn attachment_att_id_segment_may_contain_dots() {
    // The `.`-free guard applies to the slug (segment[0]) ONLY; the att-id segment
    // legitimately carries `.enc` and is unharmed by additional dots — proving the
    // guard is not a blanket "reject any dotted segment".
    assert_eq!(
        classify_path("attachments/eng/a1b2c3d4e5f6a1b2/00112233.aux.enc"),
        PathClass::Item { collection: "eng".to_string() }
    );
}
