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 "GetDocumentHistoryRequest" => {
178 load_expectation::<proto::GetDocumentHistoryRequest>(&mut dapi, filename)?
179 }
180 "IdentityRequest" => load_expectation::<IdentityRequest>(&mut dapi, filename)?,
181 "GetIdentityRequest" => {
182 load_expectation::<proto::GetIdentityRequest>(&mut dapi, filename)?
183 }
184
185 "GetIdentityBalanceRequest" => {
186 load_expectation::<proto::GetIdentityBalanceRequest>(&mut dapi, filename)?
187 }
188 "GetIdentityContractNonceRequest" => {
189 load_expectation::<proto::GetIdentityContractNonceRequest>(&mut dapi, filename)?
190 }
191 "GetIdentityBalanceAndRevisionRequest" => load_expectation::<
192 proto::GetIdentityBalanceAndRevisionRequest,
193 >(&mut dapi, filename)?,
194 "GetAddressInfoRequest" => {
195 load_expectation::<proto::GetAddressInfoRequest>(&mut dapi, filename)?
196 }
197 "GetAddressesInfosRequest" => {
198 load_expectation::<proto::GetAddressesInfosRequest>(&mut dapi, filename)?
199 }
200 "GetIdentityKeysRequest" => {
201 load_expectation::<proto::GetIdentityKeysRequest>(&mut dapi, filename)?
202 }
203 "GetProtocolVersionUpgradeStateRequest" => load_expectation::<
204 proto::GetProtocolVersionUpgradeStateRequest,
205 >(&mut dapi, filename)?,
206 "GetProtocolVersionUpgradeVoteStatusRequest" => {
207 load_expectation::<proto::GetProtocolVersionUpgradeVoteStatusRequest>(
208 &mut dapi, filename,
209 )?
210 }
211 "GetContestedResourcesRequest" => {
212 load_expectation::<proto::GetContestedResourcesRequest>(&mut dapi, filename)?
213 }
214 "GetContestedResourceVoteStateRequest" => load_expectation::<
215 proto::GetContestedResourceVoteStateRequest,
216 >(&mut dapi, filename)?,
217 "GetContestedResourceVotersForIdentityRequest" => {
218 load_expectation::<proto::GetContestedResourceVotersForIdentityRequest>(
219 &mut dapi, filename,
220 )?
221 }
222 "GetContestedResourceIdentityVotesRequest" => {
223 load_expectation::<proto::GetContestedResourceIdentityVotesRequest>(
224 &mut dapi, filename,
225 )?
226 }
227 "GetVotePollsByEndDateRequest" => {
228 load_expectation::<proto::GetVotePollsByEndDateRequest>(&mut dapi, filename)?
229 }
230 "GetPrefundedSpecializedBalanceRequest" => load_expectation::<
231 proto::GetPrefundedSpecializedBalanceRequest,
232 >(&mut dapi, filename)?,
233 "GetPathElementsRequest" => {
234 load_expectation::<proto::GetPathElementsRequest>(&mut dapi, filename)?
235 }
236 "GetTotalCreditsInPlatformRequest" => load_expectation::<
237 proto::GetTotalCreditsInPlatformRequest,
238 >(&mut dapi, filename)?,
239 "GetIdentityKeysRemainingBudgetsRequest" => load_expectation::<
240 proto::GetIdentityKeysRemainingBudgetsRequest,
241 >(&mut dapi, filename)?,
242 "GetIdentityTokenBalancesRequest" => {
243 load_expectation::<proto::GetIdentityTokenBalancesRequest>(&mut dapi, filename)?
244 }
245 "GetIdentitiesTokenBalancesRequest" => load_expectation::<
246 proto::GetIdentitiesTokenBalancesRequest,
247 >(&mut dapi, filename)?,
248 "GetIdentityTokenInfosRequest" => {
249 load_expectation::<proto::GetIdentityTokenInfosRequest>(&mut dapi, filename)?
250 }
251 "GetIdentitiesTokenInfosRequest" => {
252 load_expectation::<proto::GetIdentitiesTokenInfosRequest>(&mut dapi, filename)?
253 }
254 "GetTokenStatusesRequest" => {
255 load_expectation::<proto::GetTokenStatusesRequest>(&mut dapi, filename)?
256 }
257 "GetTokenTotalSupplyRequest" => {
258 load_expectation::<proto::GetTokenTotalSupplyRequest>(&mut dapi, filename)?
259 }
260 "GetGroupInfoRequest" => {
261 load_expectation::<proto::GetGroupInfoRequest>(&mut dapi, filename)?
262 }
263 "GetGroupInfosRequest" => {
264 load_expectation::<proto::GetGroupInfosRequest>(&mut dapi, filename)?
265 }
266 "GetGroupActionsRequest" => {
267 load_expectation::<proto::GetGroupActionsRequest>(&mut dapi, filename)?
268 }
269 "GetGroupActionSignersRequest" => {
270 load_expectation::<proto::GetGroupActionSignersRequest>(&mut dapi, filename)?
271 }
272 "EvoNode" => load_expectation::<EvoNode>(&mut dapi, filename)?,
273 "GetTokenDirectPurchasePricesRequest" => load_expectation::<
274 proto::GetTokenDirectPurchasePricesRequest,
275 >(&mut dapi, filename)?,
276 "GetTokenPerpetualDistributionLastClaimRequest" => {
277 load_expectation::<proto::GetTokenPerpetualDistributionLastClaimRequest>(
278 &mut dapi, filename,
279 )?
280 }
281 "GetTokenPreProgrammedDistributionsRequest" => {
282 load_expectation::<proto::GetTokenPreProgrammedDistributionsRequest>(
283 &mut dapi, filename,
284 )?
285 }
286 "GetAddressesTrunkStateRequest" => {
287 load_expectation::<proto::GetAddressesTrunkStateRequest>(&mut dapi, filename)?
288 }
289 _ => {
290 return Err(Error::Config(format!(
291 "unknown request type {} in {}, missing match arm in load_expectations?",
292 request_type,
293 filename.display()
294 )))
295 }
296 };
297 }
298
299 Ok(self)
300 }
301
302 pub async fn expect_fetch<O: Fetch + MockResponse, Q: Query<<O as Fetch>::Query>>(
353 &mut self,
354 query: Q,
355 object: Option<O>,
356 ) -> Result<&mut Self, Error>
357 where
358 <<O as Fetch>::Request as TransportRequest>::Response: Default,
359 {
360 let (rich, wire) =
361 self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
362 self.expect(&rich, wire, object).await?;
363
364 Ok(self)
365 }
366
367 pub async fn remove_fetch_expectation<O, Q>(&mut self, query: Q) -> bool
371 where
372 O: Fetch,
373 Q: Query<<O as Fetch>::Query>,
374 {
375 let (rich, wire) =
376 self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
377 self.remove(&rich, wire).await
378 }
379
380 pub async fn expect_fetch_many<
411 K: Ord,
412 O: FetchMany<K, R>,
413 Q: Query<<O as FetchMany<K, R>>::Query>,
414 R,
415 >(
416 &mut self,
417 query: Q,
418 objects: Option<R>,
419 ) -> Result<&mut Self, Error>
420 where
421 R: FromIterator<(K, Option<O>)>
422 + MockResponse
423 + FromProof<
424 <O as FetchMany<K, R>>::Query,
425 Request = <O as FetchMany<K, R>>::Query,
426 Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
427 > + Sync
428 + Send
429 + Default,
430 <<O as FetchMany<K, R>>::Request as TransportRequest>::Response: Default,
431 {
432 let (rich, wire) = self
433 .encode_rich_to_wire::<Q, <O as FetchMany<K, R>>::Query, <O as FetchMany<K, R>>::Request>(
434 query,
435 );
436 self.expect(&rich, wire, objects).await?;
437
438 Ok(self)
439 }
440
441 fn encode_rich_to_wire<Q, R, W>(&self, query: Q) -> (R, W)
453 where
454 Q: Query<R>,
455 R: Query<W> + Mockable,
456 W: TransportRequest,
457 {
458 let sdk_guard = self.sdk.load();
459 let sdk = sdk_guard
460 .as_ref()
461 .expect("sdk must be set when creating mock");
462 let settings = sdk.query_settings();
463 let rich: R = query.query(&settings).expect("query must be correct");
464 let wire: W = rich.query(&settings).expect("wire encoding must succeed");
465 (rich, wire)
466 }
467
468 async fn expect<R: Mockable + std::fmt::Debug, W: TransportRequest, O: MockResponse>(
476 &mut self,
477 rich_request: &R,
478 wire_request: W,
479 returned_object: Option<O>,
480 ) -> Result<(), Error>
481 where
482 W::Response: Default,
483 {
484 let key = Key::new(rich_request);
485
486 if self.from_proof_expectations.contains_key(&key) {
487 return Err(MockError::MockExpectationConflict(format!(
488 "proof expectation key {} already defined for {} request: {:?}",
489 key,
490 std::any::type_name::<R>(),
491 rich_request
492 ))
493 .into());
494 }
495
496 self.from_proof_expectations
497 .insert(key, returned_object.mock_serialize(self));
498
499 let mut dapi_guard = self.dapi.lock().await;
500 dapi_guard.expect(
501 &wire_request,
502 &Ok(ExecutionResponse {
503 inner: Default::default(),
504 retries: 0,
505 address: "http://127.0.0.1".parse().expect("failed to parse address"),
506 }),
507 )?;
508
509 Ok(())
510 }
511
512 async fn remove<R: Mockable, W: TransportRequest>(
514 &mut self,
515 rich_request: &R,
516 wire_request: W,
517 ) -> bool {
518 let key = Key::new(rich_request);
519 let removed_from_proof = self.from_proof_expectations.remove(&key).is_some();
520
521 let mut dapi_guard = self.dapi.lock().await;
522 let removed_from_dapi = dapi_guard.remove(&wire_request);
523
524 removed_from_proof || removed_from_dapi
525 }
526
527 pub(crate) fn parse_proof_with_metadata<I, O: FromProof<I>>(
529 &self,
530 request: O::Request,
531 response: O::Response,
532 ) -> Result<(Option<O>, ResponseMetadata, Proof), drive_proof_verifier::Error>
533 where
534 O::Request: Mockable,
535 Option<O>: MockResponse,
536 {
538 let key = Key::new(&request);
539
540 let data = match self.from_proof_expectations.get(&key) {
541 Some(d) => (
545 Option::<O>::mock_deserialize(self, d),
546 ResponseMetadata {
547 protocol_version: dpp::version::LATEST_VERSION,
548 ..Default::default()
549 },
550 Proof::default(),
551 ),
552 None => {
553 let version = self.version();
554 let provider = self.context_provider()
555 .ok_or(ContextProviderError::InvalidQuorum(
556 "expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
557 ))?;
558 O::maybe_from_proof_with_metadata(
559 request,
560 response,
561 Network::Regtest,
562 version,
563 &provider,
564 )?
565 }
566 };
567
568 Ok(data)
569 }
570 fn context_provider(&self) -> Option<impl ContextProvider> {
572 if let Some(sdk) = self.sdk.load_full() {
573 sdk.clone().context_provider()
574 } else {
575 None
576 }
577 }
578}
579
580fn load_expectation<T: TransportRequest>(
586 dapi_guard: &mut OwnedMutexGuard<MockDapiClient>,
587 path: &PathBuf,
588) -> Result<(), Error> {
589 let data = DumpData::<T>::load(path)
590 .map_err(|e| {
591 Error::Config(format!(
592 "cannot load mock expectations from {}: {}",
593 path.display(),
594 e
595 ))
596 })?
597 .deserialize();
598 dapi_guard.expect(&data.0, &data.1)?;
599 Ok(())
600}