Skip to main content

drive/util/test_helpers/
mod.rs

1#[cfg(feature = "server")]
2use std::fs::File;
3#[cfg(feature = "server")]
4use std::io;
5#[cfg(feature = "server")]
6use std::io::BufRead;
7#[cfg(feature = "server")]
8use std::path::Path;
9
10#[cfg(feature = "fixtures-and-mocks")]
11use grovedb::TransactionArg;
12
13#[cfg(feature = "fixtures-and-mocks")]
14use crate::drive::Drive;
15#[cfg(feature = "fixtures-and-mocks")]
16use dpp::data_contract::DataContract;
17
18#[cfg(feature = "fixtures-and-mocks")]
19use dpp::block::block_info::BlockInfo;
20#[cfg(feature = "fixtures-and-mocks")]
21use dpp::prelude::Identifier;
22
23#[cfg(feature = "fixtures-and-mocks")]
24use crate::drive::votes::paths::vote_end_date_queries_tree_path_vec;
25#[cfg(feature = "fixtures-and-mocks")]
26use crate::query::VotePollsByEndDateDriveQuery;
27#[cfg(feature = "fixtures-and-mocks")]
28use crate::util::common::encode::decode_u64;
29#[cfg(feature = "fixtures-and-mocks")]
30use dpp::prelude::TimestampMillis;
31#[cfg(feature = "fixtures-and-mocks")]
32use dpp::tests::json_document::json_document_to_contract_with_ids;
33#[cfg(feature = "fixtures-and-mocks")]
34use dpp::version::PlatformVersion;
35#[cfg(feature = "fixtures-and-mocks")]
36use grovedb::query_result_type::QueryResultType;
37#[cfg(feature = "fixtures-and-mocks")]
38use grovedb::{PathQuery, Query};
39#[cfg(feature = "fixtures-and-mocks")]
40use std::collections::{BTreeMap, BTreeSet};
41
42#[cfg(feature = "fixtures-and-mocks")]
43use dpp::data_contract::schema::DataContractSchemaMethodsV0;
44#[cfg(feature = "fixtures-and-mocks")]
45use dpp::platform_value::Value as PlatformValue;
46
47#[cfg(test)]
48use ciborium::value::Value;
49
50#[cfg(test)]
51use crate::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfo;
52#[cfg(test)]
53use crate::util::object_size_info::DocumentInfo::DocumentRefInfo;
54#[cfg(test)]
55use crate::util::object_size_info::{DataContractOwnedResolvedInfo, OwnedDocumentInfo};
56#[cfg(test)]
57use crate::util::storage_flags::StorageFlags;
58#[cfg(test)]
59use dpp::data_contract::accessors::v0::DataContractV0Getters;
60#[cfg(test)]
61use dpp::data_contract::document_type::random_document::{
62    CreateRandomDocument, DocumentFieldFillSize, DocumentFieldFillType,
63};
64#[cfg(test)]
65use dpp::document::DocumentV0Setters;
66#[cfg(test)]
67use dpp::platform_value;
68#[cfg(test)]
69use dpp::platform_value::Bytes32;
70#[cfg(test)]
71use dpp::voting::vote_info_storage::contested_document_vote_poll_stored_info::ContestedDocumentVotePollStoredInfo;
72#[cfg(test)]
73use rand::rngs::StdRng;
74#[cfg(test)]
75use rand::SeedableRng;
76
77#[cfg(any(test, feature = "server"))]
78pub mod setup;
79#[cfg(any(test, feature = "fixtures-and-mocks"))]
80/// test utils
81pub mod test_utils;
82
83#[cfg(feature = "fixtures-and-mocks")]
84/// Applies to Drive a JSON contract from the file system.
85pub fn setup_contract(
86    drive: &Drive,
87    path: &str,
88    contract_id: Option<[u8; 32]>,
89    owner_id: Option<[u8; 32]>,
90    contract_modification: Option<impl FnOnce(&mut DataContract)>,
91    transaction: TransactionArg,
92    use_platform_version: Option<&PlatformVersion>,
93) -> DataContract {
94    let platform_version = use_platform_version.unwrap_or(PlatformVersion::latest());
95    let mut contract = json_document_to_contract_with_ids(
96        path,
97        contract_id.map(Identifier::from),
98        owner_id.map(Identifier::from),
99        false, //no need to validate the data contracts in tests for drive
100        platform_version,
101    )
102    .expect("expected to get json based contract");
103
104    if let Some(contract_modification) = contract_modification {
105        contract_modification(&mut contract);
106    }
107
108    drive
109        .apply_contract(
110            &contract,
111            BlockInfo::default(),
112            true,
113            None,
114            transaction,
115            platform_version,
116        )
117        .expect("contract should be applied");
118    contract
119}
120
121#[cfg(feature = "fixtures-and-mocks")]
122/// Every end date of the vote poll end-date queries, with the unique ids of the vote polls listed
123/// under it; an end date that lists none shows as an empty set.
124pub fn vote_poll_end_dates(
125    drive: &Drive,
126    platform_version: &PlatformVersion,
127) -> BTreeMap<TimestampMillis, BTreeSet<Identifier>> {
128    let mut query = Query::new();
129    query.insert_all();
130    let (end_dates, _) = drive
131        .grove_get_raw_path_query(
132            &PathQuery::new_unsized(vote_end_date_queries_tree_path_vec(), query),
133            None,
134            QueryResultType::QueryKeyElementPairResultType,
135            &mut vec![],
136            &platform_version.drive,
137        )
138        .expect("expected to read the end dates");
139    end_dates
140        .to_keys()
141        .into_iter()
142        .map(|end_date_key| {
143            let end_date = decode_u64(&end_date_key).expect("expected an encoded end date");
144            let unique_ids =
145                VotePollsByEndDateDriveQuery::execute_no_proof_keys_for_single_end_time(
146                    end_date,
147                    None,
148                    drive,
149                    None,
150                    &mut vec![],
151                    platform_version,
152                )
153                .expect("expected to read the vote polls of an end date")
154                .into_iter()
155                .map(|key| Identifier::from_bytes(&key).expect("expected a vote poll id"))
156                .collect();
157            (end_date, unique_ids)
158        })
159        .collect()
160}
161
162#[cfg(test)]
163/// The identity id of DPNS name contender `n` of [`add_dpns_name_contenders`]: `n + 1` big
164/// endian in its first 8 bytes, so contenders sort by `n`.
165pub(crate) fn dpns_name_contender_id(n: u64) -> Identifier {
166    let mut id = [0u8; 32];
167    id[..8].copy_from_slice(&(n + 1).to_be_bytes());
168    Identifier::from(id)
169}
170
171#[cfg(test)]
172/// Adds contenders `contenders` (see [`dpns_name_contender_id`]) to the contest on the DPNS name
173/// `label` under `dash`, written straight to Drive at `block_info` with no validation. Contender
174/// `n`'s document is created at `created_at(n)`. Contender 0 starts the contest, writing its
175/// stored info. Returns the poll.
176pub(crate) fn add_dpns_name_contenders(
177    drive: &Drive,
178    dpns_contract: &DataContract,
179    label: &str,
180    contenders: std::ops::Range<u64>,
181    created_at: impl Fn(u64) -> TimestampMillis,
182    block_info: &BlockInfo,
183    platform_version: &PlatformVersion,
184) -> ContestedDocumentResourceVotePollWithContractInfo {
185    let document_type = dpns_contract
186        .document_type_for_name("domain")
187        .expect("expected the domain document type");
188    let vote_poll = ContestedDocumentResourceVotePollWithContractInfo {
189        contract: DataContractOwnedResolvedInfo::OwnedDataContract(dpns_contract.clone()),
190        document_type_name: "domain".to_string(),
191        index_name: "parentNameAndLabel".to_string(),
192        index_values: vec![
193            platform_value::Value::Text("dash".to_string()),
194            platform_value::Value::Text(label.to_string()),
195        ],
196    };
197    let mut rng = StdRng::seed_from_u64(contenders.start);
198    for n in contenders {
199        let owner_id = dpns_name_contender_id(n);
200        let mut document = document_type
201            .random_document_with_params(
202                owner_id,
203                Bytes32::random_with_rng(&mut rng),
204                Some(created_at(n)),
205                Some(block_info.height),
206                Some(block_info.core_height),
207                DocumentFieldFillType::FillIfNotRequired,
208                DocumentFieldFillSize::MinDocumentFillSize,
209                &mut rng,
210                platform_version,
211            )
212            .expect("expected a random domain");
213        document.set("parentDomainName", "dash".into());
214        document.set("normalizedParentDomainName", "dash".into());
215        document.set("label", label.into());
216        document.set("normalizedLabel", label.into());
217        document.set("records.identity", owner_id.into());
218        document.set("subdomainRules.allowSubdomains", false.into());
219        let stored_info = (n == 0).then(|| {
220            ContestedDocumentVotePollStoredInfo::new(*block_info, platform_version)
221                .expect("expected the poll's stored info")
222        });
223        drive
224            .add_contested_document(
225                OwnedDocumentInfo {
226                    document_info: DocumentRefInfo((
227                        &document,
228                        StorageFlags::optional_default_as_cow(),
229                    )),
230                    owner_id: Some(owner_id.to_buffer()),
231                },
232                vote_poll.clone(),
233                false,
234                stored_info,
235                block_info,
236                true,
237                None,
238                platform_version,
239            )
240            .expect("expected to add the contender");
241    }
242    vote_poll
243}
244
245#[cfg(test)]
246/// Serializes a hex string to CBOR.
247pub fn cbor_from_hex(hex_string: String) -> Vec<u8> {
248    hex::decode(hex_string).expect("Decoding failed")
249}
250
251#[cfg(feature = "server")]
252/// Takes a file and returns the lines as a list of strings.
253pub fn text_file_strings(path: impl AsRef<Path>) -> Vec<String> {
254    let file = File::open(path).expect("file not found");
255    let reader = io::BufReader::new(file).lines();
256    reader.into_iter().map(|a| a.unwrap()).collect()
257}
258
259#[cfg(test)]
260/// Retrieves the value of a key from a CBOR map.
261pub fn get_key_from_cbor_map<'a>(
262    cbor_map: &'a [(Value, Value)],
263    key: &'a str,
264) -> Option<&'a Value> {
265    for (cbor_key, cbor_value) in cbor_map.iter() {
266        if !cbor_key.is_text() {
267            continue;
268        }
269
270        if cbor_key.as_text().expect("confirmed as text") == key {
271            return Some(cbor_value);
272        }
273    }
274    None
275}
276
277#[cfg(test)]
278/// Retrieves the value of a key from a CBOR map if it's a map itself.
279pub fn cbor_inner_map_value<'a>(
280    document_type: &'a [(Value, Value)],
281    key: &'a str,
282) -> Option<&'a Vec<(Value, Value)>> {
283    let key_value = get_key_from_cbor_map(document_type, key)?;
284    if let Value::Map(map_value) = key_value {
285        return Some(map_value);
286    }
287    None
288}
289
290#[cfg(feature = "fixtures-and-mocks")]
291/// The yappr-likes `contract` with its `like` type read whole through
292/// `byLiker`: without a proof, a chained read is refused when its inner index
293/// lacks a property, as a documents query through that index is, and
294/// `byLiker` lacks the like's optional hashtag; so the like keeps only what
295/// `byLiker` holds (no hashtag, no `byHashtagPost`, no `where` on `postId`,
296/// which takes the first position). Shared by Drive's and drive-abci's chained
297/// query tests.
298pub fn with_likes_read_whole_through_by_liker(
299    mut contract: DataContract,
300    platform_version: &PlatformVersion,
301) -> DataContract {
302    let mut schemas: BTreeMap<String, PlatformValue> = contract
303        .document_schemas()
304        .into_iter()
305        .map(|(name, schema)| (name, schema.clone()))
306        .collect();
307    let like = schemas.get_mut("like").expect("a like type");
308    {
309        let properties = like
310            .get_mut("properties")
311            .expect("like properties readable")
312            .expect("like properties");
313        properties.remove("hashtag").expect("a like hashtag");
314        let post_id = properties
315            .get_mut("postId")
316            .expect("like postId readable")
317            .expect("like postId");
318        post_id
319            .set_value("position", PlatformValue::U64(0))
320            .expect("postId position set");
321        post_id
322            .get_mut("refersTo")
323            .expect("postId refersTo readable")
324            .expect("postId refersTo")
325            .remove("where")
326            .expect("postId where");
327    }
328    like.get_mut("indices")
329        .expect("like indices readable")
330        .expect("like indices")
331        .as_array_mut()
332        .expect("like indices are an array")
333        .retain(|index| index.get_optional_str("name").ok().flatten() != Some("byHashtagPost"));
334    let defs = contract.schema_defs().cloned();
335    contract
336        .set_document_schemas(schemas, defs, true, &mut vec![], platform_version)
337        .expect("expected the like read whole through byLiker to parse");
338    contract
339}