From 62f569969393795fcaac53a15c0b3d2e65e41fb2 Mon Sep 17 00:00:00 2001 From: lynaDev2 Date: Sun, 28 Jun 2026 16:45:06 +0000 Subject: [PATCH] update --- contract/tests/revoke_tests.rs | 156 +++++++++++++++++++++++++++++ contract/tests/submit_tests.rs | 165 ++++++++++++++++++++++++++++++ contract/tests/transfer_tests.rs | 166 +++++++++++++++++++++++++++++++ 3 files changed, 487 insertions(+) create mode 100644 contract/tests/revoke_tests.rs create mode 100644 contract/tests/submit_tests.rs create mode 100644 contract/tests/transfer_tests.rs diff --git a/contract/tests/revoke_tests.rs b/contract/tests/revoke_tests.rs new file mode 100644 index 0000000..92d3898 --- /dev/null +++ b/contract/tests/revoke_tests.rs @@ -0,0 +1,156 @@ +use axum::http::StatusCode; +use axum_test::TestServer; +use serde_json::{json, Value}; +use std::sync::Arc; +use stellar_doc_verifier::cache::{CacheBackend, InMemoryCache}; +use stellar_doc_verifier::metrics::MetricsRegistry; +use stellar_doc_verifier::module::webhook::VerificationWebhookNotifier; +use stellar_doc_verifier::stellar::StellarClient; +use stellar_doc_verifier::{app, AppState, RevokeResponse}; + +fn create_test_state() -> AppState { + let stellar = Arc::new(StellarClient::new("https://horizon-testnet.stellar.org")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + } +} + +fn valid_hash() -> &'static str { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} + +#[tokio::test] +async fn test_valid_revocation_returns_200_with_transaction_id_and_revoked_at() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": valid_hash(), + "reason": "document compromised", + "revoked_by": "admin" + })) + .await; + + response.assert_status_ok(); + let body: RevokeResponse = response.json(); + assert!(!body.transaction_id.is_empty()); + assert!(body.revoked_at > 0); +} + +#[tokio::test] +async fn test_revoking_already_revoked_hash_returns_409() { + let state = create_test_state(); + + // Pre-seed revocation flag in cache + let revoked_key = format!("revoked:{}", valid_hash()); + state + .cache + .set(&revoked_key, &true, u64::MAX / 2) + .await + .unwrap(); + + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": valid_hash(), + "reason": "duplicate revoke", + "revoked_by": "admin" + })) + .await; + + assert_eq!(response.status_code(), StatusCode::CONFLICT); + let body: Value = response.json(); + assert!(body["error"].as_str().is_some()); +} + +#[tokio::test] +async fn test_invalid_hash_format_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": "not-a-valid-hash", + "reason": "test", + "revoked_by": "admin" + })) + .await; + + response.assert_status_bad_request(); + let body: Value = response.json(); + assert!(body["error"].as_str().is_some()); +} + +#[tokio::test] +async fn test_missing_reason_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": valid_hash(), + "revoked_by": "admin" + })) + .await; + + response.assert_status_bad_request(); +} + +#[tokio::test] +async fn test_missing_revoked_by_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": valid_hash(), + "reason": "test reason" + })) + .await; + + response.assert_status_bad_request(); +} + +#[tokio::test] +async fn test_stellar_failure_returns_500() { + let stellar = Arc::new(StellarClient::new("http://127.0.0.1:1")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + let state = AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + }; + + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/revoke") + .json(&json!({ + "document_hash": valid_hash(), + "reason": "test", + "revoked_by": "admin" + })) + .await; + + assert_eq!(response.status_code(), StatusCode::INTERNAL_SERVER_ERROR); +} \ No newline at end of file diff --git a/contract/tests/submit_tests.rs b/contract/tests/submit_tests.rs new file mode 100644 index 0000000..3e26778 --- /dev/null +++ b/contract/tests/submit_tests.rs @@ -0,0 +1,165 @@ +use axum::http::StatusCode; +use axum_test::TestServer; +use serde_json::{json, Value}; +use std::sync::Arc; +use stellar_doc_verifier::cache::{CacheBackend, InMemoryCache}; +use stellar_doc_verifier::metrics::MetricsRegistry; +use stellar_doc_verifier::module::webhook::VerificationWebhookNotifier; +use stellar_doc_verifier::stellar::StellarClient; +use stellar_doc_verifier::{app, AppState}; + +fn create_test_state() -> AppState { + let stellar = Arc::new(StellarClient::new("https://horizon-testnet.stellar.org")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + } +} + +fn valid_hash() -> &'static str { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} + +#[tokio::test] +async fn test_valid_hash_returns_200_with_success_and_transaction_id() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/submit") + .json(&json!({ + "document_hash": valid_hash(), + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + response.assert_status_ok(); + let body: Value = response.json(); + assert_eq!(body["success"], true); + assert!(body["transaction_id"].as_str().is_some()); +} + +#[tokio::test] +async fn test_hash_shorter_than_64_chars_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/submit") + .json(&json!({ + "document_hash": "abc123", + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + response.assert_status_bad_request(); + let body: Value = response.json(); + assert!(body["error"].as_str().unwrap().contains("wrong length")); +} + +#[tokio::test] +async fn test_non_hex_characters_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + // 64 chars but with non-hex 'z' + let bad_hash = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; + let response = server + .post("/submit") + .json(&json!({ + "document_hash": bad_hash, + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + response.assert_status_bad_request(); + let body: Value = response.json(); + assert!(body["error"].as_str().unwrap().contains("invalid character")); +} + +#[tokio::test] +async fn test_empty_document_hash_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/submit") + .json(&json!({ + "document_hash": "", + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + response.assert_status_bad_request(); + let body: Value = response.json(); + assert!(body["error"].as_str().is_some()); +} + +#[tokio::test] +async fn test_duplicate_hash_returns_409() { + let state = create_test_state(); + + // Pre-seed cache to simulate already submitted + let submit_key = format!("submit:{}", valid_hash()); + state + .cache + .set_raw(&submit_key, r#"{"tx_hash":"0xabc","anchored_at":1}"#, 3600) + .await + .unwrap(); + + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/submit") + .json(&json!({ + "document_hash": valid_hash(), + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + assert_eq!(response.status_code(), StatusCode::CONFLICT); + let body: Value = response.json(); + assert!(body["error"].as_str().unwrap().to_lowercase().contains("already")); +} + +#[tokio::test] +async fn test_stellar_client_failure_returns_500() { + // Point to a broken Stellar URL to force anchor failure + let stellar = Arc::new(StellarClient::new("http://127.0.0.1:1")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + let state = AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + }; + + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/submit") + .json(&json!({ + "document_hash": valid_hash(), + "document_id": "doc-001", + "submitter": "alice" + })) + .await; + + assert_eq!(response.status_code(), StatusCode::INTERNAL_SERVER_ERROR); +} \ No newline at end of file diff --git a/contract/tests/transfer_tests.rs b/contract/tests/transfer_tests.rs new file mode 100644 index 0000000..5c4dddb --- /dev/null +++ b/contract/tests/transfer_tests.rs @@ -0,0 +1,166 @@ +use axum::http::StatusCode; +use axum_test::TestServer; +use serde_json::{json, Value}; +use std::sync::Arc; +use stellar_doc_verifier::cache::{CacheBackend, InMemoryCache}; +use stellar_doc_verifier::metrics::MetricsRegistry; +use stellar_doc_verifier::module::webhook::VerificationWebhookNotifier; +use stellar_doc_verifier::stellar::StellarClient; +use stellar_doc_verifier::{app, AppState, TransferRequest, TransferResponse, compute_transfer_hash}; + +fn create_test_state() -> AppState { + let stellar = Arc::new(StellarClient::new("https://horizon-testnet.stellar.org")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + } +} + +fn valid_hash() -> &'static str { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} + +fn valid_transfer_body() -> serde_json::Value { + json!({ + "document_hash": valid_hash(), + "from_owner": "alice", + "to_owner": "bob", + "transfer_date": "2025-06-01", + "transfer_reference": "REF-001" + }) +} + +#[tokio::test] +async fn test_valid_transfer_returns_200_with_transfer_hash_and_memo() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server.post("/transfer").json(&valid_transfer_body()).await; + + response.assert_status_ok(); + let body: TransferResponse = response.json(); + assert!(!body.transfer_hash.is_empty()); + assert!(!body.memo.is_empty()); +} + +#[tokio::test] +async fn test_invalid_date_format_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/transfer") + .json(&json!({ + "document_hash": valid_hash(), + "from_owner": "alice", + "to_owner": "bob", + "transfer_date": "01-06-2025", + "transfer_reference": "REF-001" + })) + .await; + + response.assert_status_bad_request(); +} + +#[tokio::test] +async fn test_invalid_calendar_date_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/transfer") + .json(&json!({ + "document_hash": valid_hash(), + "from_owner": "alice", + "to_owner": "bob", + "transfer_date": "2025-13-01", + "transfer_reference": "REF-001" + })) + .await; + + response.assert_status_bad_request(); +} + +#[tokio::test] +async fn test_invalid_document_hash_returns_400() { + let state = create_test_state(); + let server = TestServer::new(app(state)).unwrap(); + + let response = server + .post("/transfer") + .json(&json!({ + "document_hash": "not-valid", + "from_owner": "alice", + "to_owner": "bob", + "transfer_date": "2025-06-01", + "transfer_reference": "REF-001" + })) + .await; + + response.assert_status_bad_request(); + let body: Value = response.json(); + assert!(body["error"].as_str().is_some()); +} + +#[tokio::test] +async fn test_transfer_of_revoked_document_returns_409() { + let state = create_test_state(); + + // Pre-seed revocation + let revoked_key = format!("revoked:{}", valid_hash()); + state + .cache + .set(&revoked_key, &true, u64::MAX / 2) + .await + .unwrap(); + + let server = TestServer::new(app(state)).unwrap(); + + let response = server.post("/transfer").json(&valid_transfer_body()).await; + + assert_eq!(response.status_code(), StatusCode::CONFLICT); +} + +#[tokio::test] +async fn test_transfer_hash_is_deterministic() { + let req = TransferRequest { + document_hash: valid_hash().to_string(), + from_owner: "alice".to_string(), + to_owner: "bob".to_string(), + transfer_date: "2025-06-01".to_string(), + transfer_reference: "REF-001".to_string(), + }; + + let h1 = compute_transfer_hash(&req); + let h2 = compute_transfer_hash(&req); + assert_eq!(h1, h2); +} + +#[tokio::test] +async fn test_stellar_failure_returns_500() { + let stellar = Arc::new(StellarClient::new("http://127.0.0.1:1")); + let cache = Arc::new(CacheBackend::InMemory(InMemoryCache::new())); + let metrics = Arc::new(MetricsRegistry::new()); + let notifier = Arc::new(VerificationWebhookNotifier::new()); + + let state = AppState { + stellar, + cache, + metrics, + stellar_secret_key: "test_secret".to_string(), + notifier, + }; + + let server = TestServer::new(app(state)).unwrap(); + + let response = server.post("/transfer").json(&valid_transfer_body()).await; + + assert_eq!(response.status_code(), StatusCode::INTERNAL_SERVER_ERROR); +} \ No newline at end of file