Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WASM tests #39

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ jobs:
- name: Run unit tests (cross build)
run: cross test --all-targets --all-features --target ${{ matrix.target }}

tests-wasm:
name: Unit tests (WASM)
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Run Wasm tests
run: wasm-pack test --chrome --headless
working-directory: .

test-default-disabled:
name: Unit tests with default features disabled
runs-on: ${{ matrix.os }}
Expand Down
17 changes: 15 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,22 @@ static-iref = "3.0"
thiserror = "1.0.61"
zeroize = { version = "1.8", features = ["zeroize_derive"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
c2pa = { version = "0.37.0", features = ["openssl_ffi_mutex"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
c2pa = { version = "0.37.0", features = [] }
wasm-bindgen = "0.2.95"

[dev-dependencies]
c2pa = { version = "0.37.0", features = ["file_io", "openssl_sign", "openssl_ffi_mutex"] }
httpmock = "0.7.0"
serde = { version = "1.0.197", features = ["derive"] }
tempfile = "3.10.1"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
c2pa = { version = "0.37.0", features = ["file_io", "openssl_sign", "openssl_ffi_mutex", "v1_api"] }
httpmock = "0.7.0"
tokio = { version = "1.40", features = ["macros"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
c2pa = { version = "0.37.0", features = [] }
wasm-bindgen-test = "0.3.31"
5 changes: 4 additions & 1 deletion src/builder/credential_holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// specific language governing permissions and limitations under
// each license.

use async_trait::async_trait;

use crate::SignerPayload;

/// An implementation of `CredentialHolder` is able to generate a signature over
Expand All @@ -21,7 +23,8 @@ use crate::SignerPayload;
/// methods] from the CAWG Identity Assertion specification.
///
/// [§8. Credentials, signatures, and validation methods]: https://creator-assertions.github.io/identity/1.0-draft/#_credentials_signatures_and_validation_methods
#[async_trait::async_trait]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait CredentialHolder {
/// Returns the designated `sig_type` value for this kind of credential.
fn sig_type(&self) -> &'static str;
Expand Down
3 changes: 2 additions & 1 deletion src/claim_aggregation/cose_vc_signature_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ use crate::{
/// [§3.3.1 Securing JSON-LD Verifiable Credentials with COSE]: https://w3c.github.io/vc-jose-cose/#securing-vcs-with-cose
pub struct CoseVcSignatureHandler {}

#[async_trait]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl SignatureHandler for CoseVcSignatureHandler {
fn can_handle_sig_type(sig_type: &str) -> bool {
sig_type == "cawg.identity_claims_aggregation"
Expand Down
2 changes: 2 additions & 0 deletions src/claim_aggregation/w3c_vc/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
// specific language governing permissions and limitations under
// each license.

#![allow(unused)] // TEMPORARY

use std::{fmt, ops::Deref, str::FromStr, sync::LazyLock};

use regex::Regex;
Expand Down
118 changes: 85 additions & 33 deletions src/claim_aggregation/w3c_vc/did_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
// specific language governing permissions and limitations under
// each license.

use reqwest::header;

use super::{did::Did, did_doc::DidDocument};

const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
Expand All @@ -32,19 +30,24 @@
pub(crate) static PROXY: RefCell<Option<String>> = const { RefCell::new(None) };
}

// #[cfg(not(target_arch = "wasm32"))]
use reqwest::Error as HttpError;
// #[cfg(target_arch = "wasm32")]
// use String as HttpError;

#[derive(Debug, thiserror::Error)]
pub enum DidWebError {
#[error("error building HTTP client: {0}")]
Client(reqwest::Error),
Client(HttpError),

#[error("error sending HTTP request ({0}): {1}")]
Request(String, reqwest::Error),
Request(String, HttpError),

#[error("server error: {0}")]
Server(String),

#[error("error reading HTTP response: {0}")]
Response(reqwest::Error),
Response(HttpError),

#[error("the document was not found: {0}")]
NotFound(String),
Expand All @@ -70,39 +73,88 @@
let url = to_url(method_specific_id)?;
// TODO: https://w3c-ccg.github.io/did-method-web/#in-transit-security

let mut headers = reqwest::header::HeaderMap::new();
let did_doc = get_did_doc(&url).await?;

headers.insert(
"User-Agent",
reqwest::header::HeaderValue::from_static(USER_AGENT),
);
let json = String::from_utf8(did_doc).map_err(|_| DidWebError::InvalidData(url.clone()))?;

let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.map_err(DidWebError::Client)?;

let resp = client
.get(&url)
.header(header::ACCEPT, "application/did+json")
.send()
.await
.map_err(|e| DidWebError::Request(url.to_owned(), e))?;

resp.error_for_status_ref().map_err(|err| {
if err.status() == Some(reqwest::StatusCode::NOT_FOUND) {
DidWebError::NotFound(url.clone())
} else {
DidWebError::Server(err.to_string())
}
})?;
DidDocument::from_json(&json).map_err(|_| DidWebError::InvalidData(url))
}

let document = resp.bytes().await.map_err(DidWebError::Response)?;
async fn get_did_doc(url: &str) -> Result<Vec<u8>, DidWebError> {
// #[cfg(not(target_arch = "wasm32"))]
{
use reqwest::header;

let mut headers = reqwest::header::HeaderMap::new();

headers.insert(
"User-Agent",
reqwest::header::HeaderValue::from_static(USER_AGENT),
);

let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.map_err(DidWebError::Client)?;

let resp = client
.get(url)
.header(header::ACCEPT, "application/did+json")
.send()
.await
.map_err(|e: reqwest::Error| DidWebError::Request(url.to_owned(), e))?;

resp.error_for_status_ref().map_err(|err| {
if err.status() == Some(reqwest::StatusCode::NOT_FOUND) {
DidWebError::NotFound(url.to_string())

Check warning on line 109 in src/claim_aggregation/w3c_vc/did_web.rs

View check run for this annotation

Codecov / codecov/patch

src/claim_aggregation/w3c_vc/did_web.rs#L108-L109

Added lines #L108 - L109 were not covered by tests
} else {
DidWebError::Server(err.to_string())

Check warning on line 111 in src/claim_aggregation/w3c_vc/did_web.rs

View check run for this annotation

Codecov / codecov/patch

src/claim_aggregation/w3c_vc/did_web.rs#L111

Added line #L111 was not covered by tests
}
})?;

let json =
String::from_utf8(document.to_vec()).map_err(|_| DidWebError::InvalidData(url.clone()))?;
let document = resp.bytes().await.map_err(DidWebError::Response)?;
Ok(document.to_vec())
}

DidDocument::from_json(&json).map_err(|_| DidWebError::InvalidData(url))
// #[cfg(target_arch = "wasm32")]
// {
// use wasm_bindgen::prelude::*;
// use wasm_bindgen_futures::JsFuture;
// use web_sys::{Request, RequestInit, RequestMode, Response};

// let opts = RequestInit::new();
// opts.set_method("GET");
// opts.set_mode(RequestMode::Cors);

// let request = Request::new_with_str_and_init(&url, &opts)
// .map_err(|_|
// DidWebError::Client("Request::new_with_str_and_init".to_string()))?;

// request
// .headers()
// .set("accept", "application/did+json")
// .map_err(|_| DidWebError::Client("Set headers".to_string()))?;

// let window = web_sys::window().unwrap();
// let resp_value = JsFuture::from(window.fetch_with_request(&request))
// .await
// .map_err(|_|
// DidWebError::Client("window.fetch_with_request".to_string()))?;

// assert!(resp_value.is_instance_of::<Response>());
// let resp: Response = resp_value.dyn_into().unwrap();

// JsFuture::from(
// resp.blob()
// .map_err(|_|
// DidWebError::Client("window.resp.bytes()".to_string()))?, )
// .await
// .map(|blob| {
// let array = js_sys::Uint8Array::new(&blob);
// array.to_vec()
// })
// .map_err(|e| DidWebError::Server("resp.blob()".to_string()))
// }
}

pub(crate) fn to_url(did: &str) -> Result<String, DidWebError> {
Expand Down
2 changes: 2 additions & 0 deletions src/claim_aggregation/w3c_vc/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
// specific language governing permissions and limitations under
// each license.

#![allow(unused)] // TEMPORARY

use std::{
convert::TryFrom, fmt, num::ParseIntError, result::Result, str::FromStr, string::FromUtf8Error,
};
Expand Down
3 changes: 2 additions & 1 deletion src/identity_assertion/signature_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use crate::identity_assertion::{NamedActor, SignerPayload, ValidationResult};
/// A `SignatureHandler` can read one kind of signature from an identity
/// assertion, assess the validity of the signature, and return information
/// about the corresponding credential subject.
#[async_trait]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait SignatureHandler {
/// Returns true if this handler can process a signature with
/// the given `sig_type` code.
Expand Down
33 changes: 27 additions & 6 deletions src/tests/claim_aggregation/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,37 @@
// each license.

use c2pa::ManifestStore;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}

use crate::IdentityAssertion;

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
async fn adobe_connected_identities() {
let manifest_store = ManifestStore::from_file(
"src/tests/fixtures/claim_aggregation/adobe_connected_identities.jpg",
)
.unwrap();
assert!(manifest_store.validation_status().is_none());
let test_image = include_bytes!("../fixtures/claim_aggregation/adobe_connected_identities.jpg");

let manifest_store = ManifestStore::from_bytes("jpg", test_image, true).unwrap();
let validation_status = manifest_store.validation_status();

#[cfg(target_arch = "wasm32")]
{
let log_str = format!("MS validation status = {validation_status:#?}");
log(&log_str);
}

assert!(validation_status.is_none());

let manifest = manifest_store.get_active().unwrap();
let identity: IdentityAssertion = manifest.find_assertion("cawg.identity").unwrap();
Expand Down
4 changes: 4 additions & 0 deletions src/tests/claim_aggregation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// each license.

mod interop;

#[cfg(not(target_arch = "wasm32"))]
mod test_issuer;

#[cfg(not(target_arch = "wasm32"))]
mod validation_error_cases;
mod w3c_vc;
24 changes: 17 additions & 7 deletions src/tests/claim_aggregation/validation_error_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
// specific language governing permissions and limitations under
// each license.

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;

use super::test_issuer::TestIssuer;

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
async fn default_case() {
let ti = TestIssuer::new();
ti.test_basic_case().await;
}

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_no_issuer() {
let ti = TestIssuer::from_asset_vc(
Expand All @@ -35,7 +40,8 @@ async fn error_no_issuer() {
ti.test_basic_case().await;
}

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_no_issuance_date() {
let ti = TestIssuer::from_asset_vc(
Expand All @@ -52,7 +58,8 @@ async fn error_no_issuance_date() {
ti.test_basic_case().await;
}

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_no_proof() {
let ti = TestIssuer::from_asset_vc(
Expand All @@ -71,7 +78,8 @@ async fn error_no_proof() {
}

/* TEMPORARY: Holding off on this one until SSI crate handles VC V2. :-(
#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_v1_vc() {
let ti = TestIssuer::from_asset_vc(
Expand All @@ -97,7 +105,8 @@ async fn error_v1_vc() {
}
*/

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_missing_cawg_context() {
let ti = TestIssuer::from_asset_vc(
Expand All @@ -122,7 +131,8 @@ async fn error_missing_cawg_context() {
ti.test_basic_case().await;
}

#[tokio::test]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic] // TEMPORARY until error results are implemented
async fn error_missing_cawg_type() {
let ti = TestIssuer::from_asset_vc(
Expand Down
Loading
Loading