Skip to main content

dpp/document/document_factory/
mod.rs

1mod v0;
2
3use crate::data_contract::DataContract;
4use std::collections::BTreeMap;
5
6use crate::version::PlatformVersion;
7use crate::ProtocolError;
8use derive_more::From;
9use platform_value::{Bytes32, Identifier, Value};
10
11use crate::data_contract::document_type::DocumentTypeRef;
12use crate::document::Document;
13#[cfg(feature = "extended-document")]
14use crate::document::ExtendedDocument;
15#[cfg(feature = "state-transitions")]
16use crate::state_transition::batch_transition::{
17    batched_transition::document_transition_action_type::DocumentTransitionActionType,
18    BatchTransition,
19};
20use crate::tokens::token_payment_info::TokenPaymentInfo;
21use crate::util::entropy_generator::EntropyGenerator;
22pub use v0::DocumentFactoryV0;
23
24/// # Document Factory
25///
26/// This module is responsible for creating instances of documents for a specific contract.
27///
28/// ## Versioning
29///
30/// The factory is versioned because the process of creating documents
31/// can change over time. Changes may be due to modifications in
32/// requirements, alterations in the document structure, or evolution in the
33/// dependencies of the document. Versioning allows for these changes to be
34/// tracked and managed effectively, providing flexibility to handle different
35/// versions of documents as needed.
36#[derive(From)]
37pub enum DocumentFactory {
38    /// The version 0 implementation of the data contract factory.
39    V0(DocumentFactoryV0),
40}
41
42impl DocumentFactory {
43    /// Create a new document factory knowing versions
44    pub fn new(protocol_version: u32) -> Result<Self, ProtocolError> {
45        let platform_version = PlatformVersion::get(protocol_version)?;
46        match platform_version
47            .dpp
48            .factory_versions
49            .document_factory_structure_version
50        {
51            0 => Ok(DocumentFactoryV0::new(protocol_version).into()),
52            version => Err(ProtocolError::UnknownVersionMismatch {
53                method: "DocumentFactory::new".to_string(),
54                known_versions: vec![0],
55                received: version,
56            }),
57        }
58    }
59
60    pub fn new_with_entropy_generator(
61        protocol_version: u32,
62        entropy_generator: Box<dyn EntropyGenerator>,
63    ) -> Result<Self, ProtocolError> {
64        let platform_version = PlatformVersion::get(protocol_version)?;
65        match platform_version
66            .dpp
67            .factory_versions
68            .document_factory_structure_version
69        {
70            0 => Ok(DocumentFactoryV0::new_with_entropy_generator(
71                protocol_version,
72                entropy_generator,
73            )
74            .into()),
75            version => Err(ProtocolError::UnknownVersionMismatch {
76                method: "DocumentFactory::new_with_entropy_generator".to_string(),
77                known_versions: vec![0],
78                received: version,
79            }),
80        }
81    }
82
83    pub fn create_document(
84        &self,
85        data_contract: &DataContract,
86        owner_id: Identifier,
87        document_type_name: String,
88        data: Value,
89    ) -> Result<Document, ProtocolError> {
90        match self {
91            DocumentFactory::V0(v0) => v0.create_document_without_time_based_properties(
92                data_contract,
93                owner_id,
94                document_type_name,
95                data,
96            ),
97        }
98    }
99
100    #[cfg(feature = "extended-document")]
101    pub fn create_extended_document(
102        &self,
103        data_contract: &DataContract,
104        owner_id: Identifier,
105        document_type_name: String,
106        data: Value,
107    ) -> Result<ExtendedDocument, ProtocolError> {
108        match self {
109            DocumentFactory::V0(v0) => {
110                v0.create_extended_document(data_contract, owner_id, document_type_name, data)
111            }
112        }
113    }
114
115    #[cfg(feature = "state-transitions")]
116    pub fn create_state_transition<'a>(
117        &self,
118        documents_iter: impl IntoIterator<
119            Item = (
120                DocumentTransitionActionType,
121                Vec<(
122                    Document,
123                    DocumentTypeRef<'a>,
124                    Bytes32,
125                    Option<TokenPaymentInfo>,
126                )>,
127            ),
128        >,
129        nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
130    ) -> Result<BatchTransition, ProtocolError> {
131        match self {
132            DocumentFactory::V0(v0) => v0.create_state_transition(documents_iter, nonce_counter),
133        }
134    }
135
136    #[cfg(feature = "extended-document")]
137    pub fn create_extended_from_document_buffer(
138        &self,
139        buffer: &[u8],
140        document_type_name: &str,
141        data_contract: &DataContract,
142        platform_version: &PlatformVersion,
143    ) -> Result<ExtendedDocument, ProtocolError> {
144        match self {
145            DocumentFactory::V0(v0) => v0.create_extended_from_document_buffer(
146                buffer,
147                document_type_name,
148                data_contract,
149                platform_version,
150            ),
151        }
152    }
153}
154
155#[cfg(test)]
156#[allow(clippy::type_complexity)]
157mod tests {
158    use super::*;
159    use crate::data_contract::accessors::v0::DataContractV0Getters;
160    use crate::document::DocumentV0Getters;
161    use crate::tests::fixtures::get_data_contract_fixture;
162    use crate::util::entropy_generator::EntropyGenerator;
163    use platform_value::platform_value;
164    use platform_version::version::PlatformVersion;
165
166    /// Deterministic entropy generator for tests.
167    struct TestEntropyGenerator;
168
169    impl EntropyGenerator for TestEntropyGenerator {
170        fn generate(&self) -> anyhow::Result<[u8; 32]> {
171            Ok([7u8; 32])
172        }
173    }
174
175    /// Always-failing entropy generator — used to exercise the error
176    /// surface in `DocumentFactory` when the generator itself fails.
177    struct FailingEntropyGenerator;
178
179    impl EntropyGenerator for FailingEntropyGenerator {
180        fn generate(&self) -> anyhow::Result<[u8; 32]> {
181            Err(anyhow::anyhow!("synthetic entropy failure"))
182        }
183    }
184
185    fn setup_factory() -> (DocumentFactory, DataContract) {
186        let platform_version = PlatformVersion::latest();
187        let created = get_data_contract_fixture(None, 0, platform_version.protocol_version);
188        let data_contract = created.data_contract_owned();
189        let factory = DocumentFactory::new_with_entropy_generator(
190            platform_version.protocol_version,
191            Box::new(TestEntropyGenerator),
192        )
193        .expect("factory construction should succeed");
194        (factory, data_contract)
195    }
196
197    // ----- Construction ------------------------------------------------------
198
199    #[test]
200    fn new_with_bad_protocol_version_returns_error() {
201        // An invalid protocol version should bubble out of the PlatformVersion lookup.
202        let result = DocumentFactory::new(u32::MAX);
203        assert!(
204            matches!(result, Err(ProtocolError::PlatformVersionError(_))),
205            "expected PlatformVersionError, got {:?}",
206            result.err()
207        );
208    }
209
210    #[test]
211    fn new_with_zero_protocol_version_returns_error() {
212        // `PlatformVersion::get(0)` also returns an error.
213        let result = DocumentFactory::new(0);
214        assert!(result.is_err(), "expected error for version 0");
215    }
216
217    #[test]
218    fn new_with_entropy_generator_bad_version_returns_error() {
219        let result =
220            DocumentFactory::new_with_entropy_generator(u32::MAX, Box::new(TestEntropyGenerator));
221        assert!(result.is_err());
222    }
223
224    #[test]
225    fn new_with_entropy_generator_valid_version_succeeds() {
226        let platform_version = PlatformVersion::latest();
227        let result = DocumentFactory::new_with_entropy_generator(
228            platform_version.protocol_version,
229            Box::new(TestEntropyGenerator),
230        );
231        assert!(result.is_ok());
232        assert!(matches!(result.unwrap(), DocumentFactory::V0(_)));
233    }
234
235    #[test]
236    fn new_variant_is_v0() {
237        let platform_version = PlatformVersion::latest();
238        let factory = DocumentFactory::new(platform_version.protocol_version).unwrap();
239        match factory {
240            DocumentFactory::V0(_) => {}
241        }
242    }
243
244    // ----- create_document (error paths) -------------------------------------
245
246    #[test]
247    fn create_document_with_invalid_type_returns_error() {
248        let (factory, data_contract) = setup_factory();
249        let owner_id = Identifier::from([0xAAu8; 32]);
250
251        let result = factory.create_document(
252            &data_contract,
253            owner_id,
254            "nonExistentDocType".to_string(),
255            Value::Null,
256        );
257
258        // InvalidDocumentTypeError is a DataContract error wrapped in ProtocolError.
259        assert!(result.is_err(), "expected error for unknown type");
260    }
261
262    #[test]
263    fn create_document_with_failing_entropy_returns_error() {
264        // Sanity-check: entropy generator errors surface as ProtocolError.
265        let platform_version = PlatformVersion::latest();
266        let created = get_data_contract_fixture(None, 0, platform_version.protocol_version);
267        let data_contract = created.data_contract_owned();
268        let factory = DocumentFactory::new_with_entropy_generator(
269            platform_version.protocol_version,
270            Box::new(FailingEntropyGenerator),
271        )
272        .unwrap();
273
274        let owner_id = Identifier::from([0x11u8; 32]);
275        let result = factory.create_document(
276            &data_contract,
277            owner_id,
278            "noTimeDocument".to_string(),
279            platform_value!({ "name": "x" }),
280        );
281        assert!(result.is_err(), "failing entropy generator should surface");
282    }
283
284    #[test]
285    fn create_document_happy_path_has_zero_time_based_props() {
286        let (factory, data_contract) = setup_factory();
287        let owner_id = Identifier::from([0xBBu8; 32]);
288
289        let doc = factory
290            .create_document(
291                &data_contract,
292                owner_id,
293                "noTimeDocument".to_string(),
294                platform_value!({ "name": "widget" }),
295            )
296            .expect("document should be created");
297
298        // `create_document_without_time_based_properties` is called internally;
299        // verify the time-based metadata was not populated.
300        assert_eq!(doc.owner_id(), owner_id);
301        assert_eq!(doc.created_at(), None);
302        assert_eq!(doc.updated_at(), None);
303    }
304
305    // ----- create_extended_document (error paths) ----------------------------
306
307    #[cfg(feature = "extended-document")]
308    #[test]
309    fn create_extended_document_with_invalid_type_returns_error() {
310        let (factory, data_contract) = setup_factory();
311        let owner_id = Identifier::from([0xCCu8; 32]);
312
313        let result = factory.create_extended_document(
314            &data_contract,
315            owner_id,
316            "bogusTypeName".to_string(),
317            Value::Null,
318        );
319        assert!(result.is_err());
320    }
321
322    #[cfg(feature = "extended-document")]
323    #[test]
324    fn create_extended_document_happy_path() {
325        let (factory, data_contract) = setup_factory();
326        let owner_id = Identifier::from([0xDDu8; 32]);
327
328        let result = factory.create_extended_document(
329            &data_contract,
330            owner_id,
331            "noTimeDocument".to_string(),
332            platform_value!({ "name": "z" }),
333        );
334        assert!(result.is_ok(), "extended document creation should succeed");
335        let ext = result.unwrap();
336        assert_eq!(ext.data_contract_id(), data_contract.id());
337        assert_eq!(ext.document_type_name(), "noTimeDocument");
338        // Entropy matches our deterministic generator.
339        assert_eq!(ext.entropy().to_buffer(), [7u8; 32]);
340    }
341
342    // ----- create_extended_from_document_buffer ------------------------------
343
344    #[cfg(feature = "extended-document")]
345    #[test]
346    fn create_extended_from_document_buffer_roundtrips() {
347        use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
348        let (factory, data_contract) = setup_factory();
349        let owner_id = Identifier::from([0x55u8; 32]);
350        let platform_version = PlatformVersion::latest();
351
352        let doc = factory
353            .create_document(
354                &data_contract,
355                owner_id,
356                "noTimeDocument".to_string(),
357                platform_value!({ "name": "abc" }),
358            )
359            .expect("doc should be created");
360
361        let doc_type = data_contract
362            .document_type_for_name("noTimeDocument")
363            .unwrap();
364        let bytes = doc
365            .serialize(doc_type, &data_contract, platform_version)
366            .expect("serialize");
367
368        let ext = factory
369            .create_extended_from_document_buffer(
370                bytes.as_slice(),
371                "noTimeDocument",
372                &data_contract,
373                platform_version,
374            )
375            .expect("extended doc should be parsed");
376
377        assert_eq!(ext.data_contract_id(), data_contract.id());
378        assert_eq!(ext.document_type_name(), "noTimeDocument");
379        // Buffer-derived extended docs have default (zero) entropy.
380        assert_eq!(ext.entropy(), &Bytes32::default());
381    }
382
383    #[cfg(feature = "extended-document")]
384    #[test]
385    fn create_extended_from_document_buffer_invalid_type_fails() {
386        let (factory, data_contract) = setup_factory();
387        let platform_version = PlatformVersion::latest();
388
389        let result = factory.create_extended_from_document_buffer(
390            &[0u8; 16],
391            "thisTypeDoesNotExist",
392            &data_contract,
393            platform_version,
394        );
395        assert!(result.is_err(), "unknown doc type should surface error");
396    }
397
398    #[cfg(feature = "extended-document")]
399    #[test]
400    fn create_extended_from_document_buffer_malformed_bytes_fails() {
401        let (factory, data_contract) = setup_factory();
402        let platform_version = PlatformVersion::latest();
403
404        // Totally random bytes should not deserialize as a Document.
405        let result = factory.create_extended_from_document_buffer(
406            &[0xFFu8; 6],
407            "noTimeDocument",
408            &data_contract,
409            platform_version,
410        );
411        assert!(result.is_err(), "malformed buffer should fail to decode");
412    }
413
414    // ----- create_state_transition (error paths) -----------------------------
415
416    #[cfg(feature = "state-transitions")]
417    mod state_transition_tests {
418        use super::*;
419        use crate::document::errors::DocumentError;
420        use crate::document::{DocumentV0Setters, INITIAL_REVISION};
421        use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
422        use crate::state_transition::state_transitions::document::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType;
423        use crate::state_transition::StateTransitionOwned;
424
425        fn build_doc(
426            factory: &DocumentFactory,
427            data_contract: &DataContract,
428            owner: Identifier,
429            type_name: &str,
430        ) -> Document {
431            factory
432                .create_document(
433                    data_contract,
434                    owner,
435                    type_name.to_string(),
436                    platform_value!({ "name": "x" }),
437                )
438                .expect("doc should build")
439        }
440
441        #[test]
442        fn create_state_transition_empty_iter_returns_error() {
443            let (factory, _) = setup_factory();
444            let mut nonce_counter: BTreeMap<(Identifier, Identifier), u64> = BTreeMap::new();
445            let empty: Vec<(
446                DocumentTransitionActionType,
447                Vec<(Document, DocumentTypeRef, Bytes32, Option<TokenPaymentInfo>)>,
448            )> = vec![];
449
450            let result = factory.create_state_transition(empty, &mut nonce_counter);
451            assert!(
452                matches!(
453                    result,
454                    Err(ProtocolError::Document(e)) if matches!(*e, DocumentError::NoDocumentsSuppliedError)
455                ),
456                "expected NoDocumentsSuppliedError"
457            );
458        }
459
460        #[test]
461        fn create_state_transition_outer_iter_has_empty_inner_returns_error() {
462            // An outer entry with an empty Vec should also yield NoDocumentsSupplied.
463            let (factory, _) = setup_factory();
464            let mut nonce_counter = BTreeMap::new();
465            let entries = vec![(DocumentTransitionActionType::Create, vec![])];
466            let result = factory.create_state_transition(entries, &mut nonce_counter);
467            assert!(
468                matches!(
469                    result,
470                    Err(ProtocolError::Document(e)) if matches!(*e, DocumentError::NoDocumentsSuppliedError)
471                ),
472                "expected NoDocumentsSuppliedError"
473            );
474        }
475
476        #[test]
477        fn create_state_transition_mismatched_owner_returns_error() {
478            let (factory, data_contract) = setup_factory();
479            let doc_type = data_contract
480                .document_type_for_name("noTimeDocument")
481                .unwrap();
482            let owner_a = Identifier::from([0x01u8; 32]);
483            let owner_b = Identifier::from([0x02u8; 32]);
484            let doc_a = build_doc(&factory, &data_contract, owner_a, "noTimeDocument");
485            let doc_b = build_doc(&factory, &data_contract, owner_b, "noTimeDocument");
486
487            let mut nonce_counter = BTreeMap::new();
488            let entries = vec![(
489                DocumentTransitionActionType::Create,
490                vec![
491                    (doc_a, doc_type, Bytes32::new([1u8; 32]), None),
492                    (doc_b, doc_type, Bytes32::new([2u8; 32]), None),
493                ],
494            )];
495            let result = factory.create_state_transition(entries, &mut nonce_counter);
496            assert!(
497                matches!(
498                    result,
499                    Err(ProtocolError::Document(e))
500                        if matches!(*e, DocumentError::MismatchOwnerIdsError { .. })
501                ),
502                "expected MismatchOwnerIdsError"
503            );
504        }
505
506        #[test]
507        fn create_state_transition_create_wrong_initial_revision_errors() {
508            let (factory, data_contract) = setup_factory();
509            let doc_type = data_contract
510                .document_type_for_name("noTimeDocument")
511                .unwrap();
512            let owner = Identifier::from([0x05u8; 32]);
513            let mut doc = build_doc(&factory, &data_contract, owner, "noTimeDocument");
514            doc.set_revision(Some(9999));
515
516            let mut nonce_counter = BTreeMap::new();
517            let entries = vec![(
518                DocumentTransitionActionType::Create,
519                vec![(doc, doc_type, Bytes32::default(), None)],
520            )];
521            let result = factory.create_state_transition(entries, &mut nonce_counter);
522            assert!(
523                matches!(
524                    result,
525                    Err(ProtocolError::Document(e))
526                        if matches!(*e, DocumentError::InvalidInitialRevisionError { .. })
527                ),
528                "expected InvalidInitialRevisionError"
529            );
530        }
531
532        #[test]
533        fn create_state_transition_create_missing_revision_on_mutable_errors() {
534            let (factory, data_contract) = setup_factory();
535            let doc_type = data_contract
536                .document_type_for_name("noTimeDocument")
537                .unwrap();
538            let owner = Identifier::from([0x06u8; 32]);
539            let mut doc = build_doc(&factory, &data_contract, owner, "noTimeDocument");
540            doc.set_revision(None);
541
542            let mut nonce_counter = BTreeMap::new();
543            let entries = vec![(
544                DocumentTransitionActionType::Create,
545                vec![(doc, doc_type, Bytes32::default(), None)],
546            )];
547            let result = factory.create_state_transition(entries, &mut nonce_counter);
548            assert!(
549                matches!(
550                    result,
551                    Err(ProtocolError::Document(e))
552                        if matches!(*e, DocumentError::RevisionAbsentError { .. })
553                ),
554                "expected RevisionAbsentError"
555            );
556        }
557
558        #[test]
559        fn create_state_transition_replace_missing_revision_errors() {
560            let (factory, data_contract) = setup_factory();
561            let doc_type = data_contract
562                .document_type_for_name("noTimeDocument")
563                .unwrap();
564            let owner = Identifier::from([0x07u8; 32]);
565            let mut doc = build_doc(&factory, &data_contract, owner, "noTimeDocument");
566            doc.set_revision(None);
567
568            let mut nonce_counter = BTreeMap::new();
569            let entries = vec![(
570                DocumentTransitionActionType::Replace,
571                vec![(doc, doc_type, Bytes32::default(), None)],
572            )];
573            let result = factory.create_state_transition(entries, &mut nonce_counter);
574            assert!(
575                matches!(
576                    result,
577                    Err(ProtocolError::Document(e))
578                        if matches!(*e, DocumentError::RevisionAbsentError { .. })
579                ),
580                "expected RevisionAbsentError for replace"
581            );
582        }
583
584        #[test]
585        fn create_state_transition_delete_missing_revision_errors() {
586            let (factory, data_contract) = setup_factory();
587            let doc_type = data_contract
588                .document_type_for_name("noTimeDocument")
589                .unwrap();
590            let owner = Identifier::from([0x08u8; 32]);
591            let mut doc = build_doc(&factory, &data_contract, owner, "noTimeDocument");
592            doc.set_revision(None);
593
594            let mut nonce_counter = BTreeMap::new();
595            let entries = vec![(
596                DocumentTransitionActionType::Delete,
597                vec![(doc, doc_type, Bytes32::default(), None)],
598            )];
599            let result = factory.create_state_transition(entries, &mut nonce_counter);
600            assert!(
601                matches!(
602                    result,
603                    Err(ProtocolError::Document(e))
604                        if matches!(*e, DocumentError::RevisionAbsentError { .. })
605                ),
606                "expected RevisionAbsentError for delete"
607            );
608        }
609
610        #[test]
611        fn create_state_transition_create_increments_nonce() {
612            let (factory, data_contract) = setup_factory();
613            let doc_type = data_contract
614                .document_type_for_name("noTimeDocument")
615                .unwrap();
616            let owner = Identifier::from([0x20u8; 32]);
617            let doc = build_doc(&factory, &data_contract, owner, "noTimeDocument");
618
619            let mut nonce_counter = BTreeMap::new();
620            nonce_counter.insert((owner, data_contract.id()), 7);
621
622            let entries = vec![(
623                DocumentTransitionActionType::Create,
624                vec![(doc, doc_type, Bytes32::default(), None)],
625            )];
626            let batch = factory
627                .create_state_transition(entries, &mut nonce_counter)
628                .expect("should build");
629            assert_eq!(batch.owner_id(), owner);
630            assert_eq!(batch.transitions_len(), 1);
631            // Pre-seeded nonce 7 → 8.
632            assert_eq!(*nonce_counter.get(&(owner, data_contract.id())).unwrap(), 8);
633        }
634
635        #[test]
636        fn create_state_transition_mix_actions_combines_transitions() {
637            let (factory, data_contract) = setup_factory();
638            let doc_type = data_contract
639                .document_type_for_name("noTimeDocument")
640                .unwrap();
641            let owner = Identifier::from([0x30u8; 32]);
642
643            // Two create docs (same owner, distinct ids).
644            let mut c1 = build_doc(&factory, &data_contract, owner, "noTimeDocument");
645            c1.set_id(Identifier::from([0xAAu8; 32]));
646            let mut c2 = build_doc(&factory, &data_contract, owner, "noTimeDocument");
647            c2.set_id(Identifier::from([0xBBu8; 32]));
648
649            // One replace doc — must be mutable + have revision.
650            let mut r1 = build_doc(&factory, &data_contract, owner, "noTimeDocument");
651            r1.set_id(Identifier::from([0xCCu8; 32]));
652            assert_eq!(r1.revision(), Some(INITIAL_REVISION));
653
654            let mut nonce_counter = BTreeMap::new();
655            let entries = vec![
656                (
657                    DocumentTransitionActionType::Create,
658                    vec![
659                        (c1, doc_type, Bytes32::new([0x01; 32]), None),
660                        (c2, doc_type, Bytes32::new([0x02; 32]), None),
661                    ],
662                ),
663                (
664                    DocumentTransitionActionType::Replace,
665                    vec![(r1, doc_type, Bytes32::default(), None)],
666                ),
667            ];
668            let batch = factory
669                .create_state_transition(entries, &mut nonce_counter)
670                .expect("mixed batch should build");
671            assert_eq!(batch.transitions_len(), 3);
672            // 2 creates + 1 replace = nonce increments 3 times for same (owner, contract).
673            assert_eq!(*nonce_counter.get(&(owner, data_contract.id())).unwrap(), 3);
674        }
675    }
676}
677
678//
679// #[cfg(test)]
680// mod old_disabled_test {
681//     use platform_value::btreemap_extensions::BTreeValueMapHelper;
682//     use platform_value::platform_value;
683//     use platform_value::string_encoding::Encoding;
684//     use std::sync::Arc;
685//
686//     use crate::tests::fixtures::get_extended_documents_fixture;
687//     use crate::{
688//         assert_error_contains,
689//         state_repository::MockStateRepositoryLike,
690//         tests::{
691//             fixtures::{get_data_contract_fixture, get_document_validator_fixture},
692//             utils::generate_random_identifier_struct,
693//         },
694//     };
695//     use crate::document::document_factory::DocumentFactory;
696//
697//     use super::*;
698//
699//     #[test]
700//     fn document_with_type_and_data() {
701//         let mut data_contract = get_data_contract_fixture(None).data_contract;
702//         let document_type = "niceDocument";
703//
704//         let factory = DocumentFactory::new(
705//             1,
706//             get_document_validator_fixture(),
707//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
708//         );
709//         let name = "Cutie";
710//         let contract_id = Identifier::from_string(
711//             "FQco85WbwNgb5ix8QQAH6wurMcgEC5ENSCv5ixG9cj12",
712//             Encoding::Base58,
713//         )
714//             .unwrap();
715//         let owner_id = Identifier::from_string(
716//             "5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq",
717//             Encoding::Base58,
718//         )
719//             .unwrap();
720//
721//         data_contract.id = contract_id;
722//
723//         let document = factory
724//             .create_extended_document_for_state_transition(
725//                 data_contract,
726//                 owner_id,
727//                 document_type.to_string(),
728//                 platform_value!({ "name": name }),
729//             )
730//             .expect("document creation shouldn't fail");
731//         assert_eq!(document_type, document.document_type_name);
732//         assert_eq!(
733//             name,
734//             document
735//                 .properties()
736//                 .get_str("name")
737//                 .expect("property 'name' should exist")
738//         );
739//         assert_eq!(contract_id, document.data_contract_id);
740//         assert_eq!(owner_id, document.owner_id());
741//         assert_eq!(
742//             document_transition::INITIAL_REVISION,
743//             *document.revision().unwrap()
744//         );
745//         assert!(!document.id().to_string(Encoding::Base58).is_empty());
746//         assert!(document.created_at().is_some());
747//     }
748//
749//     #[test]
750//     fn create_state_transition_no_documents() {
751//         let factory = DocumentFactory::new(
752//             1,
753//             get_document_validator_fixture(),
754//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
755//         );
756//
757//         let result = factory.create_state_transition(vec![]);
758//         assert_error_contains!(result, "No documents were supplied to state transition")
759//     }
760//
761//     #[test]
762//     fn create_transition_mismatch_user_id() {
763//         let data_contract = get_data_contract_fixture(None).data_contract;
764//         let mut documents = get_extended_documents_fixture(data_contract).unwrap();
765//
766//         let factory = DocumentFactory::new(
767//             1,
768//             get_document_validator_fixture(),
769//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
770//         );
771//
772//         documents[0].document.owner_id = generate_random_identifier_struct();
773//
774//         let result = factory.create_state_transition(vec![(DocumentTransitionActionType::Create, documents)]);
775//         assert_error_contains!(result, "Documents have mixed owner ids")
776//     }
777//
778//     #[test]
779//     fn create_transition_invalid_initial_revision() {
780//         let data_contract = get_data_contract_fixture(None).data_contract;
781//         let mut documents = get_extended_documents_fixture(data_contract).unwrap();
782//         documents[0].document.revision = Some(3);
783//
784//         let factory = DocumentFactory::new(
785//             1,
786//             get_document_validator_fixture(),
787//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
788//         );
789//         let result = factory.create_state_transition(vec![(DocumentTransitionActionType::Create, documents)]);
790//         assert_error_contains!(result, "Invalid Document initial revision '3'")
791//     }
792//
793//     #[test]
794//     fn create_transitions_with_passed_documents() {
795//         let data_contract = get_data_contract_fixture(None).data_contract;
796//         let documents = get_extended_documents_fixture(data_contract).unwrap();
797//         let factory = DocumentFactory::new(
798//             1,
799//             get_document_validator_fixture(),
800//             DataContractFetcherAndValidator::new(Arc::new(MockStateRepositoryLike::new())),
801//         );
802//
803//         let new_document = documents[0].clone();
804//         let batch_transition = factory
805//             .create_state_transition(vec![
806//                 (DocumentTransitionActionType::Create, documents),
807//                 (DocumentTransitionActionType::Replace, vec![new_document]),
808//             ])
809//             .expect("state transitions should be created");
810//         assert_eq!(11, batch_transition.transitions.len());
811//         assert_eq!(
812//             10,
813//             batch_transition
814//                 .transitions
815//                 .iter()
816//                 .filter(|t| t.as_transition_create().is_some())
817//                 .count()
818//         );
819//         assert_eq!(
820//             1,
821//             batch_transition
822//                 .transitions
823//                 .iter()
824//                 .filter(|t| t.as_transition_replace().is_some())
825//                 .count()
826//         )
827//     }
828// }