Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
82e4593
update Cargo.toml with correct metadata
maxmalkin Mar 2, 2026
25c76c9
add integration test crate skeleton
maxmalkin Mar 2, 2026
268f0ad
add integration test helpers and infrastructure
maxmalkin Mar 2, 2026
607be01
add happy path integration tests
maxmalkin Mar 2, 2026
0108066
add token verification denial tests
maxmalkin Mar 2, 2026
6ac31d2
add revocation propagation tests
maxmalkin Mar 2, 2026
6c4b37d
add concurrency and race condition tests
maxmalkin Mar 2, 2026
a461188
add audit log integrity tests
maxmalkin Mar 2, 2026
11a5ae3
add idempotency tests
maxmalkin Mar 2, 2026
528a8c2
add stability test crate skeleton
maxmalkin Mar 2, 2026
256fad5
add stability test helpers and infrastructure
maxmalkin Mar 2, 2026
0186a32
add verifier throughput stability test
maxmalkin Mar 2, 2026
48ac59f
add memory leak soak test
maxmalkin Mar 2, 2026
b1e2fe7
add concurrent grant stress test
maxmalkin Mar 2, 2026
4317d73
add dependency failure recovery tests
maxmalkin Mar 2, 2026
64e38dd
add audit hash chain integrity test
maxmalkin Mar 2, 2026
59d230e
update license to MIT in test crates
maxmalkin Mar 2, 2026
33a1f5f
fix seed functions to match actual database schema
maxmalkin Mar 2, 2026
3fe751a
include response status and body in parse_json panic message
maxmalkin Mar 2, 2026
6605cd2
fix audit tests to match flat array response format
maxmalkin Mar 2, 2026
2858ae6
add status assertions to concurrency test setup flow
maxmalkin Mar 2, 2026
dc150c2
fix envelope factory: omit null fields that cannot deserialize into Vec
maxmalkin Mar 2, 2026
f936403
create audit partition for current month in test setup
maxmalkin Mar 2, 2026
0d174ec
pull README
maxmalkin Mar 2, 2026
200ce0a
fix capability JSON format to use internally tagged representation
maxmalkin Mar 2, 2026
1d0716d
handle duplicate partition error in test setup
maxmalkin Mar 2, 2026
7e6f337
fix grant flood test to use distinct service providers
maxmalkin Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ members = [
"services/verifier",
"services/audit-archiver",
"tests/compliance",
"tests/integration",
"tests/stability",
]

[workspace.package]
version = "0.1.0"
edition = "2021"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
repository = "https://github.com/agentauth/agentauth"
authors = ["AgentAuth Contributors"]
license = "MIT"
repository = "https://github.com/maxmalkin/AgentAuth"
authors = ["Max Malkin"]

[workspace.dependencies]
# Async runtime
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,4 @@ Target performance characteristics:

## License

MIT License
MIT License
2 changes: 1 addition & 1 deletion tests/compliance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "compliance-tests"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
license = "MIT"
publish = false

[[test]]
Expand Down
39 changes: 39 additions & 0 deletions tests/integration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "integration-tests"
version = "0.1.0"
edition = "2021"
license = "MIT"
publish = false

[[test]]
name = "integration"
path = "main.rs"

[dependencies]
auth_core = { package = "core", path = "../../crates/core" }
registry = { path = "../../crates/registry" }

tokio = { version = "1.36", features = ["full", "test-util"] }
axum = { version = "0.7", features = ["macros"] }
tower = { version = "0.4", features = ["util"] }
hyper = { version = "1.2", features = ["full"] }
http-body-util = "0.1"

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.7", features = ["v7", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
base64 = "0.22"
hex = "0.4"
rand = "0.8"
ed25519-dalek = { version = "2.1", features = ["serde", "rand_core"] }
sha2 = "0.10"
subtle = "2.5"
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls", "postgres", "uuid", "chrono", "json", "macros", "migrate"] }
redis = { version = "0.25", features = ["tokio-comp", "connection-manager"] }
async-trait = "0.1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }

[lints]
workspace = true
224 changes: 224 additions & 0 deletions tests/integration/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//! Audit log integrity tests.

use crate::helpers::assertions::{assert_status, parse_json};
use crate::helpers::factories;
use crate::helpers::setup::{seed_human_principal, seed_service_provider, Body, Request, TestApp};
use hyper::StatusCode;

/// Audit event is written on agent registration.
#[tokio::test]
async fn test_audit_written_on_registration() {
let app = TestApp::new().await;
let (register_body, agent_id, hp_id, _sp_id) = factories::create_signed_agent(&app.signer);
seed_human_principal(&app.db_pool, hp_id).await;

// Register
let req = Request::builder()
.method("POST")
.uri("/v1/agents/register")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&register_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);

// Check audit log
let req = Request::builder()
.method("GET")
.uri(&format!("/v1/audit/{agent_id}"))
.body(Body::empty())
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);
let body = parse_json(resp).await;

// Response is a flat array of audit events
let events = body.as_array().expect("response should be a JSON array");
let has_registration = events.iter().any(|e| e["event_type"] == "agent_registered");
assert!(
has_registration,
"audit log should contain agent_registered event"
);
}

/// Audit events are written for the full grant lifecycle.
#[tokio::test]
async fn test_audit_written_on_grant_lifecycle() {
let app = TestApp::new().await;
let (register_body, agent_id, hp_id, sp_id) = factories::create_signed_agent(&app.signer);
seed_human_principal(&app.db_pool, hp_id).await;
seed_service_provider(&app.db_pool, sp_id).await;

// Register
let req = Request::builder()
.method("POST")
.uri("/v1/agents/register")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&register_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);

// Request grant
let grant_body = factories::create_grant_request(agent_id, sp_id);
let req = Request::builder()
.method("POST")
.uri("/v1/grants/request")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&grant_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);
let body = parse_json(resp).await;
let grant_id: uuid::Uuid = serde_json::from_value(body["id"].clone()).unwrap();

// Approve
let approve_body = factories::create_approve_request(hp_id);
let req = Request::builder()
.method("POST")
.uri(&format!("/v1/grants/{grant_id}/approve"))
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&approve_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);

// Issue token
let issue_body = factories::create_issue_request(grant_id, agent_id, sp_id, hp_id);
let req = Request::builder()
.method("POST")
.uri("/v1/tokens/issue")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&issue_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);

// Check audit log for all expected events
let req = Request::builder()
.method("GET")
.uri(&format!("/v1/audit/{agent_id}"))
.body(Body::empty())
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);
let body = parse_json(resp).await;

let events = body.as_array().expect("response should be a JSON array");
let event_types: Vec<&str> = events
.iter()
.filter_map(|e| e["event_type"].as_str())
.collect();

assert!(
event_types.contains(&"agent_registered"),
"should have agent_registered"
);
assert!(
event_types.contains(&"grant_requested"),
"should have grant_requested"
);
assert!(
event_types.contains(&"grant_approved"),
"should have grant_approved"
);
assert!(
event_types.contains(&"token_issued"),
"should have token_issued"
);
}

/// Audit event is written on grant denial.
#[tokio::test]
async fn test_audit_written_on_denial() {
let app = TestApp::new().await;
let (register_body, agent_id, hp_id, sp_id) = factories::create_signed_agent(&app.signer);
seed_human_principal(&app.db_pool, hp_id).await;
seed_service_provider(&app.db_pool, sp_id).await;

// Register
let req = Request::builder()
.method("POST")
.uri("/v1/agents/register")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&register_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);

// Request grant
let grant_body = factories::create_grant_request(agent_id, sp_id);
let req = Request::builder()
.method("POST")
.uri("/v1/grants/request")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&grant_body).unwrap()))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::CREATED);
let body = parse_json(resp).await;
let grant_id: uuid::Uuid = serde_json::from_value(body["id"].clone()).unwrap();

// Deny
let req = Request::builder()
.method("POST")
.uri(&format!("/v1/grants/{grant_id}/deny"))
.header("content-type", "application/json")
.body(Body::from("{}"))
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);

// Check audit
let req = Request::builder()
.method("GET")
.uri(&format!("/v1/audit/{agent_id}"))
.body(Body::empty())
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);
let body = parse_json(resp).await;

let events = body.as_array().expect("response should be a JSON array");
let has_denial = events.iter().any(|e| e["event_type"] == "grant_denied");
assert!(has_denial, "audit log should contain grant_denied event");
}

/// Audit hash chain integrity can be verified.
#[tokio::test]
async fn test_audit_chain_integrity() {
let app = TestApp::new().await;
let (register_body, agent_id, hp_id, sp_id) = factories::create_signed_agent(&app.signer);
seed_human_principal(&app.db_pool, hp_id).await;
seed_service_provider(&app.db_pool, sp_id).await;

// Register (creates audit event)
let req = Request::builder()
.method("POST")
.uri("/v1/agents/register")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&register_body).unwrap()))
.unwrap();
let _ = app.registry_request(req).await;

// Request grant (creates audit event)
let grant_body = factories::create_grant_request(agent_id, sp_id);
let req = Request::builder()
.method("POST")
.uri("/v1/grants/request")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&grant_body).unwrap()))
.unwrap();
let _ = app.registry_request(req).await;

// Verify chain integrity
let req = Request::builder()
.method("GET")
.uri(&format!("/v1/audit/{agent_id}/verify"))
.body(Body::empty())
.unwrap();
let resp = app.registry_request(req).await;
assert_status(&resp, StatusCode::OK);
let body = parse_json(resp).await;
assert_eq!(body["valid"], true, "audit chain should be valid");
}
Loading
Loading