diff --git a/rust/signed_doc/src/builder.rs b/rust/signed_doc/src/builder.rs index ceb559af2e9..1f0c9ba85ca 100644 --- a/rust/signed_doc/src/builder.rs +++ b/rust/signed_doc/src/builder.rs @@ -92,7 +92,8 @@ impl ContentBuilder { json: &serde_json::Value, ) -> anyhow::Result { anyhow::ensure!( - self.metadata.content_type() == Some(ContentType::Json), + self.metadata.content_type() == Some(ContentType::Json) + || self.metadata.content_type() == Some(ContentType::SchemaJson), "Already set metadata field `content-type` is not JSON value" ); diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index d7f42823d09..7758b4d906b 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -5,182 +5,24 @@ pub(crate) mod rules; use std::{collections::HashMap, sync::LazyLock}; -use catalyst_types::catalyst_id::role_index::RoleId; -use rules::{ - ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, OriginalAuthorRule, - ParametersRule, RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule, -}; +use rules::Rules; use crate::{ - doc_types::{ - BRAND_PARAMETERS, CAMPAIGN_PARAMETERS, CATEGORY_PARAMETERS, PROPOSAL, PROPOSAL_COMMENT, - PROPOSAL_COMMENT_FORM_TEMPLATE, PROPOSAL_FORM_TEMPLATE, PROPOSAL_SUBMISSION_ACTION, - }, metadata::DocType, providers::{CatalystIdProvider, CatalystSignedDocumentProvider}, - validator::rules::{CollaboratorsRule, SignatureRule, TemplateRule}, - CatalystSignedDocument, ContentEncoding, ContentType, + CatalystSignedDocument, }; /// A table representing a full set or validation rules per document id. static DOCUMENT_RULES: LazyLock> = LazyLock::new(document_rules_init); -/// Proposal -/// Require field: type, id, ver, template, parameters -/// -fn proposal_rule() -> Rules { - // Parameter can be either brand, campaign or category - let parameters = vec![ - BRAND_PARAMETERS.clone(), - CAMPAIGN_PARAMETERS.clone(), - CATEGORY_PARAMETERS.clone(), - ]; - Rules { - id: IdRule, - ver: VerRule, - content_type: ContentTypeRule::Specified { - exp: ContentType::Json, - }, - content_encoding: ContentEncodingRule::Specified { - exp: vec![ContentEncoding::Brotli], - optional: false, - }, - template: TemplateRule::Specified { - allowed_type: PROPOSAL_FORM_TEMPLATE.clone(), - }, - parameters: ParametersRule::Specified { - allowed_type: parameters.clone(), - optional: false, - }, - doc_ref: RefRule::NotSpecified, - reply: ReplyRule::NotSpecified, - section: SectionRule::NotSpecified, - collaborators: CollaboratorsRule::NotSpecified, - content: ContentRule::NotNil, - kid: SignatureKidRule { - allowed_roles: [RoleId::Proposer].into_iter().collect(), - }, - signature: SignatureRule { mutlisig: false }, - original_author: OriginalAuthorRule, - } -} - -/// Proposal Comment -/// Require field: type, id, ver, ref, template, parameters -/// -fn proposal_comment_rule() -> Rules { - // Parameter can be either brand, campaign or category - let parameters = vec![ - BRAND_PARAMETERS.clone(), - CAMPAIGN_PARAMETERS.clone(), - CATEGORY_PARAMETERS.clone(), - ]; - Rules { - id: IdRule, - ver: VerRule, - content_type: ContentTypeRule::Specified { - exp: ContentType::Json, - }, - content_encoding: ContentEncodingRule::Specified { - exp: vec![ContentEncoding::Brotli], - optional: false, - }, - template: TemplateRule::Specified { - allowed_type: PROPOSAL_COMMENT_FORM_TEMPLATE.clone(), - }, - doc_ref: RefRule::Specified { - allowed_type: vec![PROPOSAL.clone()], - multiple: false, - optional: false, - }, - reply: ReplyRule::Specified { - allowed_type: PROPOSAL_COMMENT.clone(), - optional: true, - }, - section: SectionRule::NotSpecified, - parameters: ParametersRule::Specified { - allowed_type: parameters.clone(), - optional: false, - }, - collaborators: CollaboratorsRule::NotSpecified, - content: ContentRule::NotNil, - kid: SignatureKidRule { - allowed_roles: [RoleId::Role0].into_iter().collect(), - }, - signature: SignatureRule { mutlisig: false }, - original_author: OriginalAuthorRule, - } -} - -/// Proposal Submission Action -/// Require fields: type, id, ver, ref, parameters -/// -#[allow(clippy::expect_used)] -fn proposal_submission_action_rule() -> Rules { - // Parameter can be either brand, campaign or category - let parameters = vec![ - BRAND_PARAMETERS.clone(), - CAMPAIGN_PARAMETERS.clone(), - CATEGORY_PARAMETERS.clone(), - ]; - - let proposal_action_json_schema_content = &serde_json::from_str(include_str!( - "./../../../../specs/definitions/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json" - )) - .expect("Must be a valid json file"); - - let proposal_action_json_schema = - json_schema::JsonSchema::try_from(proposal_action_json_schema_content) - .expect("Must be a valid json scheme file"); - - Rules { - id: IdRule, - ver: VerRule, - content_type: ContentTypeRule::Specified { - exp: ContentType::Json, - }, - content_encoding: ContentEncodingRule::Specified { - exp: vec![ContentEncoding::Brotli], - optional: false, - }, - template: TemplateRule::NotSpecified, - parameters: ParametersRule::Specified { - allowed_type: parameters, - optional: false, - }, - doc_ref: RefRule::Specified { - allowed_type: vec![PROPOSAL.clone()], - multiple: false, - optional: false, - }, - reply: ReplyRule::NotSpecified, - section: SectionRule::NotSpecified, - collaborators: CollaboratorsRule::NotSpecified, - content: ContentRule::StaticSchema(ContentSchema::Json(proposal_action_json_schema)), - kid: SignatureKidRule { - allowed_roles: [RoleId::Proposer].into_iter().collect(), - }, - signature: SignatureRule { mutlisig: false }, - original_author: OriginalAuthorRule, - } -} - /// `DOCUMENT_RULES` initialization function #[allow(clippy::expect_used)] fn document_rules_init() -> HashMap { - let mut document_rules_map: HashMap = Rules::documents_rules() + let document_rules_map: HashMap = Rules::documents_rules() .expect("cannot fail to initialize validation rules") .collect(); - // TODO: remove this redefinitions of the validation rules after - // `catalyst_signed_documents_rules!` macro would be fully finished - document_rules_map.insert(PROPOSAL.clone(), proposal_rule()); - document_rules_map.insert(PROPOSAL_COMMENT.clone(), proposal_comment_rule()); - document_rules_map.insert( - PROPOSAL_SUBMISSION_ACTION.clone(), - proposal_submission_action_rule(), - ); - document_rules_map } diff --git a/rust/signed_doc/src/validator/rules/mod.rs b/rust/signed_doc/src/validator/rules/mod.rs index 58f56dce992..d004bd75cc7 100644 --- a/rust/signed_doc/src/validator/rules/mod.rs +++ b/rust/signed_doc/src/validator/rules/mod.rs @@ -25,7 +25,7 @@ mod utils; mod ver; pub(crate) use collaborators::CollaboratorsRule; -pub(crate) use content::{ContentRule, ContentSchema}; +pub(crate) use content::ContentRule; pub(crate) use content_encoding::ContentEncodingRule; pub(crate) use content_type::ContentTypeRule; pub(crate) use doc_ref::RefRule; diff --git a/rust/signed_doc/src/validator/rules/template.rs b/rust/signed_doc/src/validator/rules/template.rs index 7ba8418af9a..40f0397cf31 100644 --- a/rust/signed_doc/src/validator/rules/template.rs +++ b/rust/signed_doc/src/validator/rules/template.rs @@ -84,10 +84,9 @@ impl TemplateRule { return false; }; match template_content_type { - ContentType::Json | ContentType::SchemaJson => { - templated_json_schema_check(doc, template_doc) - }, - ContentType::Cddl + ContentType::SchemaJson => templated_json_schema_check(doc, template_doc), + ContentType::Json + | ContentType::Cddl | ContentType::Cbor | ContentType::Css | ContentType::CssHandlebars @@ -98,7 +97,7 @@ impl TemplateRule { | ContentType::Plain | ContentType::PlainHandlebars => { // TODO: not implemented yet - true + false }, } }; @@ -183,7 +182,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -212,7 +211,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -238,7 +237,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -265,7 +264,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -293,7 +292,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(UuidV4::new().into())) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -321,7 +320,7 @@ mod tests { let doc = Builder::new() .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); @@ -377,7 +376,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .build(); provider.add_document(None, &doc).unwrap(); @@ -404,7 +403,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(vec![1,2 ,3]) .build(); provider.add_document(None, &doc).unwrap(); @@ -478,7 +477,7 @@ mod tests { .with_metadata_field(SupportedField::Id(*template_ref.id())) .with_metadata_field(SupportedField::Ver(*template_ref.ver())) .with_metadata_field(SupportedField::Type(allowed_type)) - .with_metadata_field(SupportedField::ContentType(ContentType::Json)) + .with_metadata_field(SupportedField::ContentType(ContentType::SchemaJson)) .with_content(json_schema) .build(); provider.add_document(None, &doc).unwrap(); diff --git a/rust/signed_doc/tests/comment.rs b/rust/signed_doc/tests/comment.rs deleted file mode 100644 index e41b959d971..00000000000 --- a/rust/signed_doc/tests/comment.rs +++ /dev/null @@ -1,346 +0,0 @@ -//! Test for Proposal Comment document. -//! Require fields: type, id, ver, ref, template, parameters -//! - -use std::sync::LazyLock; - -use catalyst_signed_doc::{providers::tests::TestCatalystProvider, *}; -use catalyst_types::catalyst_id::role_index::RoleId; -use ed25519_dalek::ed25519::signature::Signer; - -use crate::common::create_dummy_key_pair; - -mod common; - -#[allow(clippy::unwrap_used)] -static DUMMY_PROPOSAL_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "type": doc_types::PROPOSAL.clone(), - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .empty_content() - .unwrap() - .build() - .unwrap() -}); - -#[allow(clippy::unwrap_used)] -static DUMMY_BRAND_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "type": doc_types::BRAND_PARAMETERS.clone(), - })) - .unwrap() - .empty_content() - .unwrap() - .build() - .unwrap() -}); - -#[allow(clippy::unwrap_used)] -static COMMENT_TEMPLATE_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT_FORM_TEMPLATE.clone(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": {}, - "required": [], - "additionalProperties": false - })) - .unwrap() - .build() - .unwrap() -}); - -#[allow(clippy::unwrap_used)] -static COMMENT_REF_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "template": { - "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap() -}); - -// Given a proposal comment document `doc`: -// -// - Parameters: -// The `parameters` field in `doc` points to a brand document. -// The parameter rule defines the link reference as `template`, This mean the document -// that `ref` field in `doc` points to (in this case = template_doc), must have the same -// `parameters` value as `doc`. -// -// - Reply: -// The `reply` field in `doc` points to another comment (`ref_doc`). -// The rule requires that the `ref` field in `ref_doc` must match the `ref` field in `doc` -#[tokio::test] -async fn test_valid_comment_doc() { - let (sk, pk, kid) = create_dummy_key_pair(RoleId::Role0).unwrap(); - let mut provider = TestCatalystProvider::default(); - provider.add_pk(kid.clone(), pk); - - // Create a main comment doc, contain all fields mention in the document (except - // revocations and section) - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "template": { - "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "reply": { - "id": COMMENT_REF_DOC.doc_id().unwrap(), - "ver": COMMENT_REF_DOC.doc_ver().unwrap() - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &COMMENT_REF_DOC).unwrap(); - provider.add_document(None, &COMMENT_TEMPLATE_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(is_valid, "{:?}", doc.problem_report()); - assert!(is_valid); - assert!(!doc.problem_report().is_problematic()); -} - -#[tokio::test] -async fn test_invalid_comment_doc_wrong_role() { - let (sk, _pk, kid) = create_dummy_key_pair(RoleId::Proposer).unwrap(); - - // Create a main comment doc, contain all fields mention in the document (except - // revocations and section) - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "template": { - "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "reply": { - "id": COMMENT_REF_DOC.doc_id().unwrap(), - "ver": COMMENT_REF_DOC.doc_ver().unwrap() - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &COMMENT_REF_DOC).unwrap(); - provider.add_document(None, &COMMENT_TEMPLATE_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid, "{:?}", doc.problem_report()); -} - -#[tokio::test] -async fn test_invalid_comment_doc_missing_parameters() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "template": { - "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "reply": { - "id": COMMENT_REF_DOC.doc_id().unwrap(), - "ver": COMMENT_REF_DOC.doc_ver().unwrap() - }, - // "parameters": { - // "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - // "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - // } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &COMMENT_REF_DOC).unwrap(); - provider.add_document(None, &COMMENT_TEMPLATE_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_comment_doc_missing_template() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - // "template": { - // "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - // "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - // }, - "reply": { - "id": COMMENT_REF_DOC.doc_id().unwrap(), - "ver": COMMENT_REF_DOC.doc_ver().unwrap() - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &COMMENT_REF_DOC).unwrap(); - provider.add_document(None, &COMMENT_TEMPLATE_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_comment_doc_missing_ref() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_COMMENT.clone(), - "id": id, - "ver": id, - // "ref": { - // "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - // "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - // }, - "template": { - "id": COMMENT_TEMPLATE_DOC.doc_id().unwrap(), - "ver": COMMENT_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "reply": { - "id": COMMENT_REF_DOC.doc_id().unwrap(), - "ver": COMMENT_REF_DOC.doc_ver().unwrap() - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &COMMENT_REF_DOC).unwrap(); - provider.add_document(None, &COMMENT_TEMPLATE_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} diff --git a/rust/signed_doc/tests/common/brand_parameters.rs b/rust/signed_doc/tests/common/brand_parameters.rs new file mode 100644 index 00000000000..c1354e7ae96 --- /dev/null +++ b/rust/signed_doc/tests/common/brand_parameters.rs @@ -0,0 +1,13 @@ +use super::*; + +pub fn brand_parameters_doc() -> anyhow::Result { + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "id": UuidV7::new(), + "ver": UuidV7::new(), + "type": doc_types::BRAND_PARAMETERS.clone(), + }))? + .empty_content()? + .build() +} diff --git a/rust/signed_doc/tests/common/campaign_parameters.rs b/rust/signed_doc/tests/common/campaign_parameters.rs new file mode 100644 index 00000000000..99ced28e6bd --- /dev/null +++ b/rust/signed_doc/tests/common/campaign_parameters.rs @@ -0,0 +1,13 @@ +use super::*; + +pub fn campaign_parameters_doc() -> anyhow::Result { + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "id": UuidV7::new(), + "ver": UuidV7::new(), + "type": doc_types::CAMPAIGN_PARAMETERS.clone(), + }))? + .empty_content()? + .build() +} diff --git a/rust/signed_doc/tests/common/category_parameters.rs b/rust/signed_doc/tests/common/category_parameters.rs new file mode 100644 index 00000000000..cbd0bcb9c8b --- /dev/null +++ b/rust/signed_doc/tests/common/category_parameters.rs @@ -0,0 +1,13 @@ +use super::*; + +pub fn category_parameters_doc() -> anyhow::Result { + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "id": UuidV7::new(), + "ver": UuidV7::new(), + "type": doc_types::CATEGORY_PARAMETERS.clone(), + }))? + .empty_content()? + .build() +} diff --git a/rust/signed_doc/tests/common/mod.rs b/rust/signed_doc/tests/common/mod.rs index 8cac6323486..eeb2959bb4a 100644 --- a/rust/signed_doc/tests/common/mod.rs +++ b/rust/signed_doc/tests/common/mod.rs @@ -1,9 +1,26 @@ -#![allow(dead_code)] +#![allow(dead_code, unused_imports)] + +pub mod brand_parameters; +pub mod campaign_parameters; +pub mod category_parameters; +pub mod proposal; +pub mod proposal_comment; +pub mod proposal_comment_form_template; +pub mod proposal_form_template; +pub mod proposal_submission_action; use std::str::FromStr; +pub use brand_parameters::brand_parameters_doc; +pub use campaign_parameters::campaign_parameters_doc; use catalyst_signed_doc::*; use catalyst_types::catalyst_id::role_index::RoleId; +pub use category_parameters::category_parameters_doc; +pub use proposal::proposal_doc; +pub use proposal_comment::proposal_comment_doc; +pub use proposal_comment_form_template::proposal_comment_form_template_doc; +pub use proposal_form_template::proposal_form_template_doc; +pub use proposal_submission_action::proposal_submission_action_doc; pub fn create_dummy_key_pair( role_index: RoleId diff --git a/rust/signed_doc/tests/common/proposal.rs b/rust/signed_doc/tests/common/proposal.rs new file mode 100644 index 00000000000..edd55ed84df --- /dev/null +++ b/rust/signed_doc/tests/common/proposal.rs @@ -0,0 +1,35 @@ +use catalyst_signed_doc::providers::tests::TestCatalystProvider; +use ed25519_dalek::ed25519::signature::Signer; + +use super::*; + +/// Creates a Proposal doc, contain all fields mention in the document spec (except +/// 'collaborators' and 'revocations') +pub fn proposal_doc( + template_doc: &CatalystSignedDocument, + parameters_doc: &CatalystSignedDocument, + provider: &mut TestCatalystProvider, +) -> anyhow::Result { + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "template": { + "id": template_doc.doc_id()?, + "ver": template_doc.doc_ver()?, + }, + "parameters": { + "id": parameters_doc.doc_id()?, + "ver": parameters_doc.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() +} diff --git a/rust/signed_doc/tests/common/proposal_comment.rs b/rust/signed_doc/tests/common/proposal_comment.rs new file mode 100644 index 00000000000..fdd9cba5e20 --- /dev/null +++ b/rust/signed_doc/tests/common/proposal_comment.rs @@ -0,0 +1,39 @@ +use catalyst_signed_doc::providers::tests::TestCatalystProvider; +use ed25519_dalek::ed25519::signature::Signer; + +use super::*; + +/// Creates a Proposal Comment doc, without reply metadata field +pub fn proposal_comment_doc( + ref_doc: &CatalystSignedDocument, + template_doc: &CatalystSignedDocument, + parameters_doc: &CatalystSignedDocument, + provider: &mut TestCatalystProvider, +) -> anyhow::Result { + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": ref_doc.doc_id()?, + "ver": ref_doc.doc_ver()?, + }, + "template": { + "id": template_doc.doc_id()?, + "ver": template_doc.doc_ver()?, + }, + "parameters": { + "id": parameters_doc.doc_id()?, + "ver": parameters_doc.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() +} diff --git a/rust/signed_doc/tests/common/proposal_comment_form_template.rs b/rust/signed_doc/tests/common/proposal_comment_form_template.rs new file mode 100644 index 00000000000..694277e7c24 --- /dev/null +++ b/rust/signed_doc/tests/common/proposal_comment_form_template.rs @@ -0,0 +1,20 @@ +use super::*; + +pub fn proposal_comment_form_template_doc( + parameters_doc: &CatalystSignedDocument +) -> anyhow::Result { + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::SchemaJson.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT_FORM_TEMPLATE.clone(), + "id": UuidV7::new(), + "ver": UuidV7::new(), + "parameters": { + "id": parameters_doc.doc_id()?, + "ver": parameters_doc.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .build() +} diff --git a/rust/signed_doc/tests/common/proposal_form_template.rs b/rust/signed_doc/tests/common/proposal_form_template.rs new file mode 100644 index 00000000000..c3c477aa805 --- /dev/null +++ b/rust/signed_doc/tests/common/proposal_form_template.rs @@ -0,0 +1,21 @@ +use super::*; + +pub fn proposal_form_template_doc( + parameters_doc: &CatalystSignedDocument +) -> anyhow::Result { + let id = UuidV7::new(); + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::SchemaJson.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_FORM_TEMPLATE.clone(), + "id": id, + "ver": id, + "parameters": { + "id": parameters_doc.doc_id()?, + "ver": parameters_doc.doc_ver()?, + }, + }))? + .with_json_content(&serde_json::json!({}))? + .build() +} diff --git a/rust/signed_doc/tests/common/proposal_submission_action.rs b/rust/signed_doc/tests/common/proposal_submission_action.rs new file mode 100644 index 00000000000..861ff36197f --- /dev/null +++ b/rust/signed_doc/tests/common/proposal_submission_action.rs @@ -0,0 +1,35 @@ +use catalyst_signed_doc::providers::tests::TestCatalystProvider; +use ed25519_dalek::ed25519::signature::Signer; + +use super::*; + +pub fn proposal_submission_action_doc( + ref_doc: &CatalystSignedDocument, + parameters_doc: &CatalystSignedDocument, + provider: &mut TestCatalystProvider, +) -> anyhow::Result { + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": ref_doc.doc_id()?, + "ver": ref_doc.doc_ver()?, + }, + "parameters": { + "id": parameters_doc.doc_id()?, + "ver": parameters_doc.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({ + "action": "final" + }))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() +} diff --git a/rust/signed_doc/tests/proposal.rs b/rust/signed_doc/tests/proposal.rs index 11cf465c6a3..70a130f500e 100644 --- a/rust/signed_doc/tests/proposal.rs +++ b/rust/signed_doc/tests/proposal.rs @@ -1,213 +1,200 @@ //! Integration test for proposal document validation part. //! Require fields: type, id, ver, template, parameters -//! - -use std::sync::LazyLock; +//! use catalyst_signed_doc::{providers::tests::TestCatalystProvider, *}; use catalyst_types::catalyst_id::role_index::RoleId; use ed25519_dalek::ed25519::signature::Signer; +use test_case::test_case; -use crate::common::create_dummy_key_pair; +use crate::common::{ + brand_parameters_doc, campaign_parameters_doc, category_parameters_doc, create_dummy_key_pair, + proposal_doc, proposal_form_template_doc, +}; mod common; -#[allow(clippy::unwrap_used)] -static DUMMY_BRAND_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "type": doc_types::BRAND_PARAMETERS.clone(), - })) - .unwrap() - .empty_content() - .unwrap() - .build() - .unwrap() -}); - -#[allow(clippy::unwrap_used)] -static PROPOSAL_TEMPLATE_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_FORM_TEMPLATE.clone(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_doc(&template, ¶meters, provider) + } + => true + ; + "valid document with brand 'parameters'" +)] +#[test_case( + |provider| { + let parameters = campaign_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_doc(&template, ¶meters, provider) + } + => true + ; + "valid document with campaign 'parameters'" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_doc(&template, ¶meters, provider) + } + => true + ; + "valid document with category 'parameters'" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, }, - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": {}, - "required": [], - "additionalProperties": false - })) - .unwrap() - .build() - .unwrap() -}); - -// Given a proposal document `doc`: -// -// - Parameters: -// The `parameters` field in `doc` points to a brand document. -// The parameter rule defines the link reference as `template`, This mean the document -// that `ref` field in `doc` points to (in this case = `template_doc`), must have the same -// `parameters` value as `doc`. -#[tokio::test] -async fn test_valid_proposal_doc() { - let (sk, pk, kid) = create_dummy_key_pair(RoleId::Proposer).unwrap(); - let mut provider = TestCatalystProvider::default(); - provider.add_pk(kid.clone(), pk); - - // Create a main proposal doc, contain all fields mention in the document (except - // collaborations and revocations) - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL.clone(), - "id": id, - "ver": id, - "template": { - "id": PROPOSAL_TEMPLATE_DOC.doc_id().unwrap(), - "ver": PROPOSAL_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - provider.add_document(None, &PROPOSAL_TEMPLATE_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(is_valid); - assert!(is_valid); - assert!(!doc.problem_report().is_problematic()); -} - -#[tokio::test] -async fn test_invalid_proposal_doc_wrong_role() { - let (sk, _pk, kid) = create_dummy_key_pair(RoleId::Role0).unwrap(); - - // Create a main proposal doc, contain all fields mention in the document (except - // collaborations and revocations) - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL.clone(), - "id": id, - "ver": id, - "template": { - "id": PROPOSAL_TEMPLATE_DOC.doc_id().unwrap(), - "ver": PROPOSAL_TEMPLATE_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &PROPOSAL_TEMPLATE_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_proposal_doc_missing_template() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL.clone(), - "id": id, - "ver": id, - // "template": { - // "id": PROPOSAL_TEMPLATE_DOC.doc_id().unwrap(), - // "ver": PROPOSAL_TEMPLATE_DOC.doc_ver().unwrap(), - // }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &PROPOSAL_TEMPLATE_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "wrong role" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .empty_content()? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "empty content" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => true + ; + "missing 'content-encoding' (optional)" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "missing template" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL.clone(), + "id": id, + "ver": id, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "missing parameters" +)] #[tokio::test] -async fn test_invalid_proposal_doc_missing_parameters() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL.clone(), - "id": id, - "ver": id, - "template": { - "id": PROPOSAL_TEMPLATE_DOC.doc_id().unwrap(), - "ver": PROPOSAL_TEMPLATE_DOC.doc_ver().unwrap(), - }, - // "parameters": { - // "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - // "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - // } - })) - .unwrap() - .with_json_content(&serde_json::json!({})) - .unwrap() - .build() - .unwrap(); - +#[allow(clippy::unwrap_used)] +async fn test_proposal_doc( + doc_gen: impl FnOnce(&mut TestCatalystProvider) -> anyhow::Result +) -> bool { let mut provider = TestCatalystProvider::default(); - provider.add_document(None, &PROPOSAL_TEMPLATE_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); + let doc = doc_gen(&mut provider).unwrap(); let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); + assert_eq!(is_valid, !doc.problem_report().is_problematic()); + println!("{:?}", doc.problem_report()); + is_valid } diff --git a/rust/signed_doc/tests/proposal_comment.rs b/rust/signed_doc/tests/proposal_comment.rs new file mode 100644 index 00000000000..73e121837ad --- /dev/null +++ b/rust/signed_doc/tests/proposal_comment.rs @@ -0,0 +1,320 @@ +//! Test for Proposal Comment document. +//! Require fields: type, id, ver, ref, template, parameters +//! + +use catalyst_signed_doc::{providers::tests::TestCatalystProvider, *}; +use catalyst_types::catalyst_id::role_index::RoleId; +use ed25519_dalek::ed25519::signature::Signer; +use test_case::test_case; + +use crate::common::{ + brand_parameters_doc, campaign_parameters_doc, category_parameters_doc, create_dummy_key_pair, + proposal_comment_doc, proposal_comment_form_template_doc, proposal_doc, + proposal_form_template_doc, +}; + +mod common; + +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_comment_doc(&proposal, &template, ¶meters, provider) + } + => true + ; + "valid document with brand 'parameters' and without 'reply'" +)] +#[test_case( + |provider| { + let parameters = campaign_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_comment_doc(&proposal, &template, ¶meters, provider) + } + => true + ; + "valid document with campaign 'parameters' and without 'reply'" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_comment_doc(&proposal, &template, ¶meters, provider) + } + => true + ; + "valid document with category 'parameters' and without 'reply'" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let comment = proposal_comment_doc(&proposal, &template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + }, + "reply": { + "id": comment.doc_id()?, + "ver": comment.doc_ver()? + }, + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => true + ; + "valid document with brand 'parameters' and with 'reply'" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + }, + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "wrong role" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .empty_content()? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing content" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => true + ; + "missing content-encoding (optional)" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing template" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing parameters" +)] +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_comment_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0) + .inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT.clone(), + "id": id, + "ver": id, + "template": { + "id": template.doc_id()?, + "ver": template.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({}))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing ref" +)] +#[tokio::test] +#[allow(clippy::unwrap_used)] +async fn test_proposal_comment_doc( + doc_gen: impl FnOnce(&mut TestCatalystProvider) -> anyhow::Result +) -> bool { + let mut provider = TestCatalystProvider::default(); + + let doc = doc_gen(&mut provider).unwrap(); + + let is_valid = validator::validate(&doc, &provider).await.unwrap(); + assert_eq!(is_valid, !doc.problem_report().is_problematic()); + println!("{:?}", doc.problem_report()); + is_valid +} diff --git a/rust/signed_doc/tests/proposal_submission_action.rs b/rust/signed_doc/tests/proposal_submission_action.rs new file mode 100644 index 00000000000..176ee9ee86b --- /dev/null +++ b/rust/signed_doc/tests/proposal_submission_action.rs @@ -0,0 +1,251 @@ +//! Test for Proposal Submission Action. +//! Require fields: type, id, ver, ref, parameters +//! + +use catalyst_signed_doc::{providers::tests::TestCatalystProvider, *}; +use catalyst_types::catalyst_id::role_index::RoleId; +use ed25519_dalek::ed25519::signature::Signer; +use test_case::test_case; + +use crate::common::{ + brand_parameters_doc, campaign_parameters_doc, category_parameters_doc, create_dummy_key_pair, + proposal_doc, proposal_form_template_doc, proposal_submission_action_doc, +}; + +mod common; + +#[test_case( + |provider| { + let parameters = brand_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_submission_action_doc(&proposal, ¶meters, provider) + } + => true + ; + "valid document with brand 'parameters'" +)] +#[test_case( + |provider| { + let parameters = campaign_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_submission_action_doc(&proposal, ¶meters, provider) + } + => true + ; + "valid document with campaign 'parameters'" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + proposal_submission_action_doc(&proposal, ¶meters, provider) + } + => true + ; + "valid document with category 'parameters'" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({ + "action": "final" + }))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build() + } + => false + ; + "wrong role" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .empty_content()? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing content" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!("null"))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "corrupted content" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({ + "action": "final" + }))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => true + ; + "missing content-encoding (optional)" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "parameters": { + "id": parameters.doc_id()?, + "ver": parameters.doc_ver()?, + } + }))? + .with_json_content(&serde_json::json!({ + "action": "final" + }))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing ref" +)] +#[test_case( + |provider| { + let parameters = category_parameters_doc().inspect(|v| provider.add_document(None, v).unwrap())?; + let template = proposal_form_template_doc(¶meters).inspect(|v| provider.add_document(None, v).unwrap())?; + let proposal = proposal_doc(&template, ¶meters, provider).inspect(|v| provider.add_document(None, v).unwrap())?; + let id = UuidV7::new(); + let (sk, _, kid) = create_dummy_key_pair(RoleId::Proposer).inspect(|(_, pk, kid)| provider.add_pk(kid.clone(), *pk))?; + let doc = Builder::new() + .with_json_metadata(serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), + "id": id, + "ver": id, + "ref": { + "id": proposal.doc_id()?, + "ver": proposal.doc_ver()?, + }, + }))? + .with_json_content(&serde_json::json!({ + "action": "final" + }))? + .add_signature(|m| sk.sign(&m).to_vec(), kid)? + .build()?; + Ok(doc) + } + => false + ; + "missing parameters" +)] +#[tokio::test] +#[allow(clippy::unwrap_used)] +async fn test_proposal_submission_action_doc( + doc_gen: impl FnOnce(&mut TestCatalystProvider) -> anyhow::Result +) -> bool { + let mut provider = TestCatalystProvider::default(); + + let doc = doc_gen(&mut provider).unwrap(); + + let is_valid = validator::validate(&doc, &provider).await.unwrap(); + assert_eq!(is_valid, !doc.problem_report().is_problematic()); + println!("{:?}", doc.problem_report()); + is_valid +} diff --git a/rust/signed_doc/tests/submission.rs b/rust/signed_doc/tests/submission.rs deleted file mode 100644 index 8c4e4c2a480..00000000000 --- a/rust/signed_doc/tests/submission.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Test for Proposal Submission Action. -//! Require fields: type, id, ver, ref, parameters -//! - -use std::sync::LazyLock; - -use catalyst_signed_doc::{providers::tests::TestCatalystProvider, *}; -use catalyst_types::catalyst_id::role_index::RoleId; -use ed25519_dalek::ed25519::signature::Signer; - -use crate::common::create_dummy_key_pair; - -mod common; - -#[allow(clippy::unwrap_used)] -static DUMMY_PROPOSAL_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "type": doc_types::PROPOSAL.clone(), - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .empty_content() - .unwrap() - .build() - .unwrap() -}); - -#[allow(clippy::unwrap_used)] -static DUMMY_BRAND_DOC: LazyLock = LazyLock::new(|| { - Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "id": UuidV7::new(), - "ver": UuidV7::new(), - "type": doc_types::BRAND_PARAMETERS.clone(), - })) - .unwrap() - .empty_content() - .unwrap() - .build() - .unwrap() -}); - -// Given a proposal comment document `doc`: -// -// - Parameters: -// The `parameters` field in `doc` points to a brand document. -// The parameter rule defines the link reference as `ref`, This mean the document that -// `ref` field in `doc` points to (in this case = `proposal_doc`), must have the same -// `parameters` value as `doc`. -#[tokio::test] -async fn test_valid_submission_action() { - let (sk, pk, kid) = create_dummy_key_pair(RoleId::Proposer).unwrap(); - let mut provider = TestCatalystProvider::default(); - provider.add_pk(kid.clone(), pk); - - // Create a main proposal submission doc, contain all fields mention in the document - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "action": "final" - })) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(is_valid, "{:?}", doc.problem_report()); - assert!(is_valid); - assert!(!doc.problem_report().is_problematic()); -} - -#[tokio::test] -async fn test_invalid_submission_action_wrong_role() { - let (sk, _pk, kid) = create_dummy_key_pair(RoleId::Role0).unwrap(); - - // Create a main proposal submission doc, contain all fields mention in the document - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "action": "final" - })) - .unwrap() - .add_signature(|m| sk.sign(&m).to_vec(), kid) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_submission_action_corrupted_json() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::Value::Null) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_submission_action_missing_ref() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), - "id": id, - "ver": id, - // "ref": { - // "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - // "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - // }, - "parameters": { - "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - } - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "action": "final" - })) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -} - -#[tokio::test] -async fn test_invalid_submission_action_missing_parameters() { - let id = UuidV7::new(); - let doc = Builder::new() - .with_json_metadata(serde_json::json!({ - "content-type": ContentType::Json.to_string(), - "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_SUBMISSION_ACTION.clone(), - "id": id, - "ver": id, - "ref": { - "id": DUMMY_PROPOSAL_DOC.doc_id().unwrap(), - "ver": DUMMY_PROPOSAL_DOC.doc_ver().unwrap(), - }, - // "parameters": { - // "id": DUMMY_BRAND_DOC.doc_id().unwrap(), - // "ver": DUMMY_BRAND_DOC.doc_ver().unwrap(), - // } - })) - .unwrap() - .with_json_content(&serde_json::json!({ - "action": "final" - })) - .unwrap() - .build() - .unwrap(); - - let mut provider = TestCatalystProvider::default(); - - provider.add_document(None, &DUMMY_PROPOSAL_DOC).unwrap(); - provider.add_document(None, &DUMMY_BRAND_DOC).unwrap(); - - let is_valid = validator::validate(&doc, &provider).await.unwrap(); - assert!(!is_valid); -}