use super::{ScdAuthChallengeConsumer, ScdAuthChallengeProducer};
use crate::chain::{Consumer, Producer, ProducerOutput, ProducerOutputKind};
use crate::test_utils::minimal_ctx;
use crate::types::ProbeSpec;
use http::{HeaderMap, HeaderValue};
use parlov_core::ResponseClass;
#[test]
fn auth_challenge_producer_admits_auth_challenge() {
assert!(ScdAuthChallengeProducer.admits(ResponseClass::AuthChallenge));
}
#[test]
fn auth_challenge_producer_does_not_admit_success() {
assert!(!ScdAuthChallengeProducer.admits(ResponseClass::Success));
}
#[test]
fn auth_challenge_producer_extracts_bearer_with_realm_and_scope() {
let mut headers = HeaderMap::new();
headers.insert(
http::header::WWW_AUTHENTICATE,
HeaderValue::from_static("Bearer realm=\"api\", scope=\"read:items\""),
);
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &headers);
assert_eq!(
out,
Some(ProducerOutput::AuthChallenge {
scheme: "Bearer".to_owned(),
realm: Some("api".to_owned()),
scope: Some("read:items".to_owned()),
}),
);
}
#[test]
fn auth_challenge_producer_extracts_bearer_without_params() {
let mut headers = HeaderMap::new();
headers.insert(
http::header::WWW_AUTHENTICATE,
HeaderValue::from_static("Bearer"),
);
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &headers);
assert_eq!(
out,
Some(ProducerOutput::AuthChallenge {
scheme: "Bearer".to_owned(),
realm: None,
scope: None,
}),
);
}
#[test]
fn auth_challenge_producer_extracts_basic_scheme() {
let mut headers = HeaderMap::new();
headers.insert(
http::header::WWW_AUTHENTICATE,
HeaderValue::from_static("Basic realm=\"Secure Area\""),
);
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &headers);
let Some(ProducerOutput::AuthChallenge { scheme, realm, .. }) = out else {
panic!("expected Some(AuthChallenge)");
};
assert_eq!(scheme, "Basic");
assert_eq!(realm, Some("Secure Area".to_owned()));
}
#[test]
fn auth_challenge_producer_returns_none_when_no_header() {
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &HeaderMap::new());
assert!(out.is_none(), "absent WWW-Authenticate must yield None");
}
#[test]
fn auth_challenge_producer_parses_realm_unquoted() {
let mut headers = HeaderMap::new();
headers.insert(
http::header::WWW_AUTHENTICATE,
HeaderValue::from_static("Bearer realm=api"),
);
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &headers);
let Some(ProducerOutput::AuthChallenge { realm, .. }) = out else {
panic!("expected Some(AuthChallenge)");
};
assert_eq!(realm, Some("api".to_owned()));
}
#[test]
fn auth_challenge_consumer_needs_auth_challenge() {
assert_eq!(
ScdAuthChallengeConsumer.needs(),
ProducerOutputKind::AuthChallenge
);
}
#[test]
fn auth_challenge_consumer_generates_one_spec() {
let output = ProducerOutput::AuthChallenge {
scheme: "Bearer".to_owned(),
realm: Some("api".to_owned()),
scope: None,
};
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
assert_eq!(specs.len(), 1, "must generate exactly 1 spec");
}
#[test]
fn auth_challenge_consumer_bearer_header_includes_realm() {
let output = ProducerOutput::AuthChallenge {
scheme: "Bearer".to_owned(),
realm: Some("api".to_owned()),
scope: None,
};
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
let ProbeSpec::Pair(pair) = &specs[0] else {
panic!("expected Pair at index 0");
};
let auth = pair
.baseline
.headers
.get("authorization")
.expect("authorization header must be present");
let val = auth.to_str().unwrap_or("");
assert!(
val.contains("Bearer") && val.contains("api"),
"Bearer authorization must reference realm; got {val}"
);
}
#[test]
fn auth_challenge_consumer_basic_uses_empty_credentials() {
let output = ProducerOutput::AuthChallenge {
scheme: "Basic".to_owned(),
realm: Some("Secure Area".to_owned()),
scope: None,
};
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
let ProbeSpec::Pair(pair) = &specs[0] else {
panic!("expected Pair at index 0");
};
let auth = pair
.baseline
.headers
.get("authorization")
.expect("authorization header must be present");
assert_eq!(
auth.to_str().unwrap_or(""),
"Basic Og==",
"Basic authorization must use empty credentials"
);
}
#[test]
fn auth_challenge_consumer_unknown_scheme_uses_scheme_only() {
let output = ProducerOutput::AuthChallenge {
scheme: "Digest".to_owned(),
realm: Some("realm".to_owned()),
scope: None,
};
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
let ProbeSpec::Pair(pair) = &specs[0] else {
panic!("expected Pair at index 0");
};
let auth = pair
.baseline
.headers
.get("authorization")
.expect("authorization header must be present");
assert_eq!(
auth.to_str().unwrap_or(""),
"Digest",
"unknown scheme must produce scheme-only authorization"
);
}
#[test]
fn auth_challenge_consumer_returns_empty_on_wrong_output_variant() {
let output = ProducerOutput::Location("https://example.com".to_owned());
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
assert!(
specs.is_empty(),
"non-AuthChallenge output must produce no specs"
);
}
use proptest::prelude::*;
proptest! {
#[test]
fn producer_extracted_scheme_is_non_empty(
scheme in "[A-Za-z][A-Za-z0-9]{1,15}",
) {
let raw = scheme.clone();
let Ok(val) = HeaderValue::from_str(&raw) else { return Ok(()); };
let mut headers = HeaderMap::new();
headers.insert(http::header::WWW_AUTHENTICATE, val);
let out = ScdAuthChallengeProducer.extract(ResponseClass::AuthChallenge, &headers);
if let Some(ProducerOutput::AuthChallenge { scheme: extracted, .. }) = out {
prop_assert!(!extracted.is_empty(), "extracted scheme must not be empty");
}
}
}
proptest! {
#[test]
fn consumer_always_generates_one_spec(
scheme in "[A-Za-z][A-Za-z0-9]{1,15}",
realm in proptest::option::of("[a-z]{1,20}"),
scope in proptest::option::of("[a-z:]{1,20}"),
) {
let output = ProducerOutput::AuthChallenge { scheme, realm, scope };
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
prop_assert_eq!(specs.len(), 1);
}
}
proptest! {
#[test]
fn consumer_baseline_and_probe_carry_same_authorization(
scheme in "[A-Za-z][A-Za-z0-9]{1,15}",
realm in proptest::option::of("[a-z]{1,20}"),
scope in proptest::option::of("[a-z:]{1,20}"),
) {
let output = ProducerOutput::AuthChallenge { scheme, realm, scope };
let specs = ScdAuthChallengeConsumer.generate(&minimal_ctx(), &output);
let ProbeSpec::Pair(pair) = &specs[0] else { return Ok(()); };
prop_assert_eq!(
pair.baseline.headers.get("authorization"),
pair.probe.headers.get("authorization"),
"baseline and probe must carry the same Authorization header"
);
}
}