1use super::MockResponse;
5use crate::{
6 platform::{
7 types::{evonode::EvoNode, identity::IdentityRequest},
8 Fetch, FetchMany, Query,
9 },
10 sync::block_on,
11 Error, Sdk,
12};
13use arc_swap::ArcSwapOption;
14use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
15use dapi_grpc::{
16 mock::Mockable,
17 platform::v0::{self as proto},
18};
19use dash_context_provider::{ContextProvider, ContextProviderError};
20use dpp::dashcore::Network;
21use dpp::version::PlatformVersion;
22use drive_proof_verifier::FromProof;
23use rs_dapi_client::mock::MockError;
24use rs_dapi_client::{
25 mock::{Key, MockDapiClient},
26 transport::TransportRequest,
27 DapiClient, DumpData, ExecutionResponse,
28};
29use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
30use tokio::sync::{Mutex, OwnedMutexGuard};
31
32#[derive(Debug)]
43pub struct MockDashPlatformSdk {
44 from_proof_expectations: BTreeMap<Key, Vec<u8>>,
45 dapi: Arc<Mutex<MockDapiClient>>,
46 sdk: ArcSwapOption<Sdk>,
47}
48
49impl MockDashPlatformSdk {
50 pub fn prove(&self) -> bool {
56 if let Some(sdk) = self.sdk.load().as_ref() {
57 sdk.prove()
58 } else {
59 panic!("sdk must be set when creating mock ")
60 }
61 }
62
63 pub(crate) fn new(dapi: Arc<Mutex<MockDapiClient>>) -> Self {
69 Self {
70 from_proof_expectations: Default::default(),
71 dapi,
72 sdk: ArcSwapOption::new(None),
73 }
74 }
75
76 pub(crate) fn set_sdk(&mut self, sdk: Sdk) {
77 self.sdk.store(Some(Arc::new(sdk)));
78 }
79
80 pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
90 if let Some(sdk) = self.sdk.load().as_ref() {
91 sdk.version()
92 } else {
93 panic!("sdk must be set when creating mock ")
94 }
95 }
96
97 #[deprecated(since = "1.4.0", note = "use load_expectations_sync")]
101 pub async fn load_expectations<P: AsRef<std::path::Path> + Send + 'static>(
102 &mut self,
103 dir: P,
104 ) -> Result<&mut Self, Error> {
105 self.load_expectations_sync(dir)
106 }
107
108 pub fn load_expectations_sync<P: AsRef<std::path::Path>>(
116 &mut self,
117 dir: P,
118 ) -> Result<&mut Self, Error> {
119 let prefix = DapiClient::DUMP_FILE_PREFIX;
120
121 let entries = dir.as_ref().read_dir().map_err(|e| {
122 Error::Config(format!(
123 "cannot load mock expectations from {}: {}",
124 dir.as_ref().display(),
125 e
126 ))
127 })?;
128
129 let files: Vec<PathBuf> = entries
130 .into_iter()
131 .filter_map(|x| x.ok())
132 .filter(|f| {
133 f.file_type().is_ok_and(|t| t.is_file())
134 && f.file_name().to_string_lossy().starts_with(prefix)
135 && f.file_name().to_string_lossy().ends_with(".json")
136 })
137 .map(|f| f.path())
138 .collect();
139
140 let mut dapi = block_on(self.dapi.clone().lock_owned())?;
141
142 for filename in &files {
143 let basename = filename.file_name().unwrap().to_str().unwrap();
144 let request_type = basename.split('_').nth(1).unwrap_or_default();
145
146 match request_type {
147 "GetDocumentsRequest" => {
148 load_expectation::<proto::GetDocumentsRequest>(&mut dapi, filename)?
149 }
150 "GetEpochsInfoRequest" => {
151 load_expectation::<proto::GetEpochsInfoRequest>(&mut dapi, filename)?
152 }
153 "GetDataContractRequest" => {
154 load_expectation::<proto::GetDataContractRequest>(&mut dapi, filename)?
155 }
156 "GetDataContractsRequest" => {
157 load_expectation::<proto::GetDataContractsRequest>(&mut dapi, filename)?
158 }
159 "GetDataContractsByRangeRequest" => {
160 load_expectation::<proto::GetDataContractsByRangeRequest>(&mut dapi, filename)?
161 }
162 "GetDataContractsLatestVersionsRequest" => load_expectation::<
163 proto::GetDataContractsLatestVersionsRequest,
164 >(&mut dapi, filename)?,
165 "GetDataContractHistoryRequest" => {
166 load_expectation::<proto::GetDataContractHistoryRequest>(&mut dapi, filename)?
167 }
168 "GetContractGroupInfoRequest" => {
169 load_expectation::<proto::GetContractGroupInfoRequest>(&mut dapi, filename)?
170 }
171 "GetContractGroupMembersRequest" => {
172 load_expectation::<proto::GetContractGroupMembersRequest>(&mut dapi, filename)?
173 }
174 "GetContractGroupsForContractRequest" => load_expectation::<
175 proto::GetContractGroupsForContractRequest,
176 >(&mut dapi, filename)?,
177 "GetContractModerationStatusRequest" => load_expectation::<
178 proto::GetContractModerationStatusRequest,
179 >(&mut dapi, filename)?,
180 "GetContractModerationEntriesRequest" => load_expectation::<
181 proto::GetContractModerationEntriesRequest,
182 >(&mut dapi, filename)?,
183 "GetContractDocumentRemovalsRequest" => load_expectation::<
184 proto::GetContractDocumentRemovalsRequest,
185 >(&mut dapi, filename)?,
186 "GetContractTeamActionsRequest" => {
187 load_expectation::<proto::GetContractTeamActionsRequest>(&mut dapi, filename)?
188 }
189 "GetContractTeamActionSignersRequest" => load_expectation::<
190 proto::GetContractTeamActionSignersRequest,
191 >(&mut dapi, filename)?,
192 "GetContractModerationActionCountsRequest" => {
193 load_expectation::<proto::GetContractModerationActionCountsRequest>(
194 &mut dapi, filename,
195 )?
196 }
197 "GetContractFeePotsRequest" => {
198 load_expectation::<proto::GetContractFeePotsRequest>(&mut dapi, filename)?
199 }
200 "GetDocumentHistoryRequest" => {
201 load_expectation::<proto::GetDocumentHistoryRequest>(&mut dapi, filename)?
202 }
203 "IdentityRequest" => load_expectation::<IdentityRequest>(&mut dapi, filename)?,
204 "GetIdentityRequest" => {
205 load_expectation::<proto::GetIdentityRequest>(&mut dapi, filename)?
206 }
207
208 "GetIdentityBalanceRequest" => {
209 load_expectation::<proto::GetIdentityBalanceRequest>(&mut dapi, filename)?
210 }
211 "GetIdentityContractNonceRequest" => {
212 load_expectation::<proto::GetIdentityContractNonceRequest>(&mut dapi, filename)?
213 }
214 "GetIdentityBalanceAndRevisionRequest" => load_expectation::<
215 proto::GetIdentityBalanceAndRevisionRequest,
216 >(&mut dapi, filename)?,
217 "GetAddressInfoRequest" => {
218 load_expectation::<proto::GetAddressInfoRequest>(&mut dapi, filename)?
219 }
220 "GetAddressesInfosRequest" => {
221 load_expectation::<proto::GetAddressesInfosRequest>(&mut dapi, filename)?
222 }
223 "GetIdentityKeysRequest" => {
224 load_expectation::<proto::GetIdentityKeysRequest>(&mut dapi, filename)?
225 }
226 "GetProtocolVersionUpgradeStateRequest" => load_expectation::<
227 proto::GetProtocolVersionUpgradeStateRequest,
228 >(&mut dapi, filename)?,
229 "GetProtocolVersionUpgradeVoteStatusRequest" => {
230 load_expectation::<proto::GetProtocolVersionUpgradeVoteStatusRequest>(
231 &mut dapi, filename,
232 )?
233 }
234 "GetContestedResourcesRequest" => {
235 load_expectation::<proto::GetContestedResourcesRequest>(&mut dapi, filename)?
236 }
237 "GetContestedResourceVoteStateRequest" => load_expectation::<
238 proto::GetContestedResourceVoteStateRequest,
239 >(&mut dapi, filename)?,
240 "GetContestedResourceVotersForIdentityRequest" => {
241 load_expectation::<proto::GetContestedResourceVotersForIdentityRequest>(
242 &mut dapi, filename,
243 )?
244 }
245 "GetContestedResourceIdentityVotesRequest" => {
246 load_expectation::<proto::GetContestedResourceIdentityVotesRequest>(
247 &mut dapi, filename,
248 )?
249 }
250 "GetVotePollsByEndDateRequest" => {
251 load_expectation::<proto::GetVotePollsByEndDateRequest>(&mut dapi, filename)?
252 }
253 "GetPrefundedSpecializedBalanceRequest" => load_expectation::<
254 proto::GetPrefundedSpecializedBalanceRequest,
255 >(&mut dapi, filename)?,
256 "GetPathElementsRequest" => {
257 load_expectation::<proto::GetPathElementsRequest>(&mut dapi, filename)?
258 }
259 "GetTotalCreditsInPlatformRequest" => load_expectation::<
260 proto::GetTotalCreditsInPlatformRequest,
261 >(&mut dapi, filename)?,
262 "GetIdentityKeysRemainingBudgetsRequest" => load_expectation::<
263 proto::GetIdentityKeysRemainingBudgetsRequest,
264 >(&mut dapi, filename)?,
265 "GetIdentityTokenBalancesRequest" => {
266 load_expectation::<proto::GetIdentityTokenBalancesRequest>(&mut dapi, filename)?
267 }
268 "GetIdentitiesTokenBalancesRequest" => load_expectation::<
269 proto::GetIdentitiesTokenBalancesRequest,
270 >(&mut dapi, filename)?,
271 "GetIdentityTokenInfosRequest" => {
272 load_expectation::<proto::GetIdentityTokenInfosRequest>(&mut dapi, filename)?
273 }
274 "GetIdentitiesTokenInfosRequest" => {
275 load_expectation::<proto::GetIdentitiesTokenInfosRequest>(&mut dapi, filename)?
276 }
277 "GetTokenStatusesRequest" => {
278 load_expectation::<proto::GetTokenStatusesRequest>(&mut dapi, filename)?
279 }
280 "GetTokenTotalSupplyRequest" => {
281 load_expectation::<proto::GetTokenTotalSupplyRequest>(&mut dapi, filename)?
282 }
283 "GetGroupInfoRequest" => {
284 load_expectation::<proto::GetGroupInfoRequest>(&mut dapi, filename)?
285 }
286 "GetGroupInfosRequest" => {
287 load_expectation::<proto::GetGroupInfosRequest>(&mut dapi, filename)?
288 }
289 "GetGroupActionsRequest" => {
290 load_expectation::<proto::GetGroupActionsRequest>(&mut dapi, filename)?
291 }
292 "GetGroupActionSignersRequest" => {
293 load_expectation::<proto::GetGroupActionSignersRequest>(&mut dapi, filename)?
294 }
295 "EvoNode" => load_expectation::<EvoNode>(&mut dapi, filename)?,
296 "GetTokenDirectPurchasePricesRequest" => load_expectation::<
297 proto::GetTokenDirectPurchasePricesRequest,
298 >(&mut dapi, filename)?,
299 "GetTokenPerpetualDistributionLastClaimRequest" => {
300 load_expectation::<proto::GetTokenPerpetualDistributionLastClaimRequest>(
301 &mut dapi, filename,
302 )?
303 }
304 "GetTokenPreProgrammedDistributionsRequest" => {
305 load_expectation::<proto::GetTokenPreProgrammedDistributionsRequest>(
306 &mut dapi, filename,
307 )?
308 }
309 "GetAddressesTrunkStateRequest" => {
310 load_expectation::<proto::GetAddressesTrunkStateRequest>(&mut dapi, filename)?
311 }
312 _ => {
313 return Err(Error::Config(format!(
314 "unknown request type {} in {}, missing match arm in load_expectations?",
315 request_type,
316 filename.display()
317 )))
318 }
319 };
320 }
321
322 Ok(self)
323 }
324
325 pub async fn expect_fetch<O: Fetch + MockResponse, Q: Query<<O as Fetch>::Query>>(
376 &mut self,
377 query: Q,
378 object: Option<O>,
379 ) -> Result<&mut Self, Error>
380 where
381 <<O as Fetch>::Request as TransportRequest>::Response: Default,
382 {
383 let (rich, wire) =
384 self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
385 self.expect(&rich, wire, object).await?;
386
387 Ok(self)
388 }
389
390 pub async fn remove_fetch_expectation<O, Q>(&mut self, query: Q) -> bool
394 where
395 O: Fetch,
396 Q: Query<<O as Fetch>::Query>,
397 {
398 let (rich, wire) =
399 self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
400 self.remove(&rich, wire).await
401 }
402
403 pub async fn expect_fetch_many<
434 K: Ord,
435 O: FetchMany<K, R>,
436 Q: Query<<O as FetchMany<K, R>>::Query>,
437 R,
438 >(
439 &mut self,
440 query: Q,
441 objects: Option<R>,
442 ) -> Result<&mut Self, Error>
443 where
444 R: FromIterator<(K, Option<O>)>
445 + MockResponse
446 + FromProof<
447 <O as FetchMany<K, R>>::Query,
448 Request = <O as FetchMany<K, R>>::Query,
449 Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
450 > + Sync
451 + Send
452 + Default,
453 <<O as FetchMany<K, R>>::Request as TransportRequest>::Response: Default,
454 {
455 let (rich, wire) = self
456 .encode_rich_to_wire::<Q, <O as FetchMany<K, R>>::Query, <O as FetchMany<K, R>>::Request>(
457 query,
458 );
459 self.expect(&rich, wire, objects).await?;
460
461 Ok(self)
462 }
463
464 fn encode_rich_to_wire<Q, R, W>(&self, query: Q) -> (R, W)
476 where
477 Q: Query<R>,
478 R: Query<W> + Mockable,
479 W: TransportRequest,
480 {
481 let sdk_guard = self.sdk.load();
482 let sdk = sdk_guard
483 .as_ref()
484 .expect("sdk must be set when creating mock");
485 let settings = sdk.query_settings();
486 let rich: R = query.query(&settings).expect("query must be correct");
487 let wire: W = rich.query(&settings).expect("wire encoding must succeed");
488 (rich, wire)
489 }
490
491 async fn expect<R: Mockable + std::fmt::Debug, W: TransportRequest, O: MockResponse>(
499 &mut self,
500 rich_request: &R,
501 wire_request: W,
502 returned_object: Option<O>,
503 ) -> Result<(), Error>
504 where
505 W::Response: Default,
506 {
507 let key = Key::new(rich_request);
508
509 if self.from_proof_expectations.contains_key(&key) {
510 return Err(MockError::MockExpectationConflict(format!(
511 "proof expectation key {} already defined for {} request: {:?}",
512 key,
513 std::any::type_name::<R>(),
514 rich_request
515 ))
516 .into());
517 }
518
519 self.from_proof_expectations
520 .insert(key, returned_object.mock_serialize(self));
521
522 let mut dapi_guard = self.dapi.lock().await;
523 dapi_guard.expect(
524 &wire_request,
525 &Ok(ExecutionResponse {
526 inner: Default::default(),
527 retries: 0,
528 address: "http://127.0.0.1".parse().expect("failed to parse address"),
529 }),
530 )?;
531
532 Ok(())
533 }
534
535 async fn remove<R: Mockable, W: TransportRequest>(
537 &mut self,
538 rich_request: &R,
539 wire_request: W,
540 ) -> bool {
541 let key = Key::new(rich_request);
542 let removed_from_proof = self.from_proof_expectations.remove(&key).is_some();
543
544 let mut dapi_guard = self.dapi.lock().await;
545 let removed_from_dapi = dapi_guard.remove(&wire_request);
546
547 removed_from_proof || removed_from_dapi
548 }
549
550 pub(crate) fn parse_proof_with_metadata<I, O: FromProof<I>>(
552 &self,
553 request: O::Request,
554 response: O::Response,
555 ) -> Result<(Option<O>, ResponseMetadata, Proof), drive_proof_verifier::Error>
556 where
557 O::Request: Mockable,
558 Option<O>: MockResponse,
559 {
561 let key = Key::new(&request);
562
563 let data = match self.from_proof_expectations.get(&key) {
564 Some(d) => (
568 Option::<O>::mock_deserialize(self, d),
569 ResponseMetadata {
570 protocol_version: dpp::version::LATEST_VERSION,
571 ..Default::default()
572 },
573 Proof::default(),
574 ),
575 None => {
576 let version = self.version();
577 let provider = self.context_provider()
578 .ok_or(ContextProviderError::InvalidQuorum(
579 "expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
580 ))?;
581 O::maybe_from_proof_with_metadata(
582 request,
583 response,
584 Network::Regtest,
585 version,
586 &provider,
587 )?
588 }
589 };
590
591 Ok(data)
592 }
593 fn context_provider(&self) -> Option<impl ContextProvider> {
595 if let Some(sdk) = self.sdk.load_full() {
596 sdk.clone().context_provider()
597 } else {
598 None
599 }
600 }
601}
602
603fn load_expectation<T: TransportRequest>(
609 dapi_guard: &mut OwnedMutexGuard<MockDapiClient>,
610 path: &PathBuf,
611) -> Result<(), Error> {
612 let data = DumpData::<T>::load(path)
613 .map_err(|e| {
614 Error::Config(format!(
615 "cannot load mock expectations from {}: {}",
616 path.display(),
617 e
618 ))
619 })?
620 .deserialize();
621 dapi_guard.expect(&data.0, &data.1)?;
622 Ok(())
623}