openidauthzen 0.1.0-alpha.1

OpenID AuthZEN Authorization API 1.0 — Policy Decision and Enforcement Points for Rust
Documentation
use openidauthzen::*;

#[test]
fn evaluation_request_should_serialize_all_required_fields() {
    let req = EvaluationRequest {
        subject: Subject {
            subject_type: "user".to_owned(),
            id: Some("alice@example.com".to_owned()),
            properties: None,
        },
        action: Action { name: "can_read".to_owned(), properties: None },
        resource: Resource {
            resource_type: "account".to_owned(),
            id: Some("123".to_owned()),
            properties: None,
        },
        context: None,
    };
    let json = serde_json::to_value(&req).unwrap();
    assert_eq!(json["subject"]["type"], "user");
    assert_eq!(json["action"]["name"], "can_read");
    assert_eq!(json["resource"]["type"], "account");
}

#[test]
fn evaluation_request_should_omit_context_when_none() {
    let req = EvaluationRequest {
        subject: Subject { subject_type: "user".to_owned(), id: None, properties: None },
        action: Action { name: "read".to_owned(), properties: None },
        resource: Resource { resource_type: "doc".to_owned(), id: None, properties: None },
        context: None,
    };
    let json = serde_json::to_value(&req).unwrap();
    assert!(json.get("context").is_none());
}

#[test]
fn evaluation_request_should_include_context_when_present() {
    let mut ctx: Context = serde_json::Map::new();
    ctx.insert("time".to_owned(), serde_json::json!("1985-10-26T01:22-07:00"));
    let req = EvaluationRequest {
        subject: Subject { subject_type: "user".to_owned(), id: Some("alice@example.com".to_owned()), properties: None },
        action: Action { name: "can_read".to_owned(), properties: None },
        resource: Resource { resource_type: "account".to_owned(), id: Some("123".to_owned()), properties: None },
        context: Some(ctx),
    };
    let json = serde_json::to_value(&req).unwrap();
    assert_eq!(json["context"]["time"], "1985-10-26T01:22-07:00");
}

#[test]
fn evaluation_request_should_roundtrip_spec_example() {
    let json = r#"{
        "subject": {"type": "user", "id": "alice@example.com"},
        "resource": {"type": "account", "id": "123"},
        "action": {"name": "can_read", "properties": {"method": "GET"}},
        "context": {"time": "1985-10-26T01:22-07:00"}
    }"#;
    let req: EvaluationRequest = serde_json::from_str(json).unwrap();
    assert_eq!(req.subject.subject_type, "user");
    assert_eq!(req.action.properties.as_ref().unwrap()["method"], "GET");
    assert_eq!(req.context.as_ref().unwrap()["time"], "1985-10-26T01:22-07:00");

    let roundtripped: EvaluationRequest = serde_json::from_value(serde_json::to_value(&req).unwrap()).unwrap();
    assert_eq!(roundtripped, req);
}

#[test]
fn evaluation_response_should_serialize_decision_true() {
    let resp = EvaluationResponse { decision: true, context: None };
    let json = serde_json::to_value(&resp).unwrap();
    assert_eq!(json["decision"], true);
}

#[test]
fn evaluation_response_should_serialize_decision_false() {
    let resp = EvaluationResponse { decision: false, context: None };
    let json = serde_json::to_value(&resp).unwrap();
    assert_eq!(json["decision"], false);
}

#[test]
fn evaluation_response_should_omit_context_when_none() {
    let resp = EvaluationResponse { decision: true, context: None };
    let json = serde_json::to_value(&resp).unwrap();
    assert!(json.get("context").is_none());
}

#[test]
fn evaluation_response_should_roundtrip_spec_example() {
    let json = r#"{"decision": true}"#;
    let resp: EvaluationResponse = serde_json::from_str(json).unwrap();
    assert!(resp.decision);
    assert!(resp.context.is_none());

    let roundtripped: EvaluationResponse = serde_json::from_value(serde_json::to_value(&resp).unwrap()).unwrap();
    assert_eq!(roundtripped, resp);
}

#[test]
fn evaluations_request_should_serialize_with_defaults_and_items() {
    let req = EvaluationsRequest {
        subject: Some(Subject { subject_type: "user".to_owned(), id: Some("alice@example.com".to_owned()), properties: None }),
        action: Some(Action { name: "can_read".to_owned(), properties: None }),
        resource: None,
        context: None,
        evaluations: vec![
            EvaluationItem {
                subject: None,
                action: None,
                resource: Some(Resource { resource_type: "document".to_owned(), id: Some("boxcarring.md".to_owned()), properties: None }),
                context: None,
            },
        ],
        options: None,
    };
    let json = serde_json::to_value(&req).unwrap();
    assert_eq!(json["subject"]["type"], "user");
    assert_eq!(json["action"]["name"], "can_read");
    assert_eq!(json["evaluations"][0]["resource"]["id"], "boxcarring.md");
}

#[test]
fn evaluations_request_should_omit_all_optional_fields_when_none() {
    let req = EvaluationsRequest {
        subject: None,
        action: None,
        resource: None,
        context: None,
        evaluations: vec![],
        options: None,
    };
    let json = serde_json::to_value(&req).unwrap();
    assert!(json.get("subject").is_none());
    assert!(json.get("action").is_none());
    assert!(json.get("resource").is_none());
    assert!(json.get("context").is_none());
    assert!(json.get("options").is_none());
    assert!(json["evaluations"].is_array());
}

#[test]
fn evaluations_request_should_include_options_when_present() {
    let req = EvaluationsRequest {
        subject: None,
        action: None,
        resource: None,
        context: None,
        evaluations: vec![],
        options: Some(EvaluationsOptions {
            evaluations_semantic: Some(EvaluationSemantic::DenyOnFirstDeny),
            extra: None,
        }),
    };
    let json = serde_json::to_value(&req).unwrap();
    assert_eq!(json["options"]["evaluations_semantic"], "deny_on_first_deny");
}

#[test]
fn evaluations_request_should_roundtrip_batch_spec_example() {
    let json = r#"{
        "subject": {"type": "user", "id": "alice@example.com"},
        "context": {"time": "2024-05-31T15:22-07:00"},
        "evaluations": [
            {"action": {"name": "can_read"}, "resource": {"type": "document", "id": "boxcarring.md"}},
            {"action": {"name": "can_read"}, "resource": {"type": "document", "id": "subject-search.md"}}
        ]
    }"#;
    let req: EvaluationsRequest = serde_json::from_str(json).unwrap();
    assert_eq!(req.evaluations.len(), 2);
    assert_eq!(req.subject.as_ref().unwrap().id.as_deref(), Some("alice@example.com"));

    let roundtripped: EvaluationsRequest = serde_json::from_value(serde_json::to_value(&req).unwrap()).unwrap();
    assert_eq!(roundtripped, req);
}

#[test]
fn evaluation_item_should_omit_all_fields_when_none() {
    let item = EvaluationItem {
        subject: None,
        action: None,
        resource: None,
        context: None,
    };
    let json = serde_json::to_value(&item).unwrap();
    assert!(json.as_object().unwrap().is_empty());
}

#[test]
fn evaluation_item_should_override_subject_only() {
    let item = EvaluationItem {
        subject: Some(Subject { subject_type: "service".to_owned(), id: Some("svc-1".to_owned()), properties: None }),
        action: None,
        resource: None,
        context: None,
    };
    let json = serde_json::to_value(&item).unwrap();
    assert_eq!(json["subject"]["type"], "service");
    assert!(json.get("action").is_none());
}

#[test]
fn evaluation_item_should_override_all_fields() {
    let item = EvaluationItem {
        subject: Some(Subject { subject_type: "user".to_owned(), id: Some("bob".to_owned()), properties: None }),
        action: Some(Action { name: "can_edit".to_owned(), properties: None }),
        resource: Some(Resource { resource_type: "doc".to_owned(), id: Some("1".to_owned()), properties: None }),
        context: Some({
            let mut m = serde_json::Map::new();
            m.insert("key".to_owned(), serde_json::json!("val"));
            m
        }),
    };
    let json = serde_json::to_value(&item).unwrap();
    assert_eq!(json["subject"]["id"], "bob");
    assert_eq!(json["action"]["name"], "can_edit");
    assert_eq!(json["resource"]["id"], "1");
    assert_eq!(json["context"]["key"], "val");
}

#[test]
fn evaluations_options_should_omit_semantic_when_none() {
    let opts = EvaluationsOptions {
        evaluations_semantic: None,
        extra: None,
    };
    let json = serde_json::to_value(&opts).unwrap();
    assert!(json.as_object().unwrap().is_empty());
}

#[test]
fn evaluations_options_should_serialize_semantic_when_present() {
    let opts = EvaluationsOptions {
        evaluations_semantic: Some(EvaluationSemantic::ExecuteAll),
        extra: None,
    };
    let json = serde_json::to_value(&opts).unwrap();
    assert_eq!(json["evaluations_semantic"], "execute_all");
}

#[test]
fn evaluations_options_should_flatten_extra_fields() {
    let mut extra = serde_json::Map::new();
    extra.insert("another_option".to_owned(), serde_json::json!("value"));
    let opts = EvaluationsOptions {
        evaluations_semantic: Some(EvaluationSemantic::ExecuteAll),
        extra: Some(extra),
    };
    let json = serde_json::to_value(&opts).unwrap();
    assert_eq!(json["evaluations_semantic"], "execute_all");
    assert_eq!(json["another_option"], "value");
}

#[test]
fn evaluations_options_should_deserialize_unknown_fields_into_extra() {
    let json = r#"{"evaluations_semantic": "execute_all", "another_option": "value"}"#;
    let opts: EvaluationsOptions = serde_json::from_str(json).unwrap();
    assert_eq!(opts.evaluations_semantic, Some(EvaluationSemantic::ExecuteAll));
    assert_eq!(opts.extra.as_ref().unwrap()["another_option"], "value");
}

#[test]
fn evaluation_semantic_should_serialize_execute_all() {
    let json = serde_json::to_value(EvaluationSemantic::ExecuteAll).unwrap();
    assert_eq!(json, "execute_all");
}

#[test]
fn evaluation_semantic_should_serialize_deny_on_first_deny() {
    let json = serde_json::to_value(EvaluationSemantic::DenyOnFirstDeny).unwrap();
    assert_eq!(json, "deny_on_first_deny");
}

#[test]
fn evaluation_semantic_should_serialize_permit_on_first_permit() {
    let json = serde_json::to_value(EvaluationSemantic::PermitOnFirstPermit).unwrap();
    assert_eq!(json, "permit_on_first_permit");
}

#[test]
fn evaluation_semantic_should_deserialize_execute_all() {
    let s: EvaluationSemantic = serde_json::from_str(r#""execute_all""#).unwrap();
    assert_eq!(s, EvaluationSemantic::ExecuteAll);
}

#[test]
fn evaluation_semantic_should_deserialize_deny_on_first_deny() {
    let s: EvaluationSemantic = serde_json::from_str(r#""deny_on_first_deny""#).unwrap();
    assert_eq!(s, EvaluationSemantic::DenyOnFirstDeny);
}

#[test]
fn evaluation_semantic_should_deserialize_permit_on_first_permit() {
    let s: EvaluationSemantic = serde_json::from_str(r#""permit_on_first_permit""#).unwrap();
    assert_eq!(s, EvaluationSemantic::PermitOnFirstPermit);
}

#[test]
fn evaluations_response_should_omit_decision_when_none() {
    let resp = EvaluationsResponse {
        decision: None,
        evaluations: vec![
            EvaluationResponse { decision: true, context: None },
            EvaluationResponse { decision: false, context: None },
        ],
    };
    let json = serde_json::to_value(&resp).unwrap();
    assert!(json.get("decision").is_none());
    assert_eq!(json["evaluations"].as_array().unwrap().len(), 2);
}