1use super::MockResponse;
5use crate::{
6 platform::{
7 types::{evonode::EvoNode, identity::IdentityRequest},
8 DocumentQuery, 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 platform_version: &'static PlatformVersion,
46 dapi: Arc<Mutex<MockDapiClient>>,
47 sdk: ArcSwapOption<Sdk>,
48}
49
50impl MockDashPlatformSdk {
51 pub fn prove(&self) -> bool {
57 if let Some(sdk) = self.sdk.load().as_ref() {
58 sdk.prove()
59 } else {
60 panic!("sdk must be set when creating mock ")
61 }
62 }
63
64 pub(crate) fn new(version: &'static PlatformVersion, dapi: Arc<Mutex<MockDapiClient>>) -> Self {
70 Self {
71 from_proof_expectations: Default::default(),
72 platform_version: version,
73 dapi,
74 sdk: ArcSwapOption::new(None),
75 }
76 }
77
78 pub(crate) fn set_sdk(&mut self, sdk: Sdk) {
79 self.sdk.store(Some(Arc::new(sdk)));
80 }
81
82 pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
83 self.platform_version
84 }
85
86 #[deprecated(since = "1.4.0", note = "use load_expectations_sync")]
90 pub async fn load_expectations<P: AsRef<std::path::Path> + Send + 'static>(
91 &mut self,
92 dir: P,
93 ) -> Result<&mut Self, Error> {
94 self.load_expectations_sync(dir)
95 }
96
97 pub fn load_expectations_sync<P: AsRef<std::path::Path>>(
105 &mut self,
106 dir: P,
107 ) -> Result<&mut Self, Error> {
108 let prefix = DapiClient::DUMP_FILE_PREFIX;
109
110 let entries = dir.as_ref().read_dir().map_err(|e| {
111 Error::Config(format!(
112 "cannot load mock expectations from {}: {}",
113 dir.as_ref().display(),
114 e
115 ))
116 })?;
117
118 let files: Vec<PathBuf> = entries
119 .into_iter()
120 .filter_map(|x| x.ok())
121 .filter(|f| {
122 f.file_type().is_ok_and(|t| t.is_file())
123 && f.file_name().to_string_lossy().starts_with(prefix)
124 && f.file_name().to_string_lossy().ends_with(".json")
125 })
126 .map(|f| f.path())
127 .collect();
128
129 let mut dapi = block_on(self.dapi.clone().lock_owned())?;
130
131 for filename in &files {
132 let basename = filename.file_name().unwrap().to_str().unwrap();
133 let request_type = basename.split('_').nth(1).unwrap_or_default();
134
135 match request_type {
136 "DocumentQuery" => load_expectation::<DocumentQuery>(&mut dapi, filename)?,
137 "GetEpochsInfoRequest" => {
138 load_expectation::<proto::GetEpochsInfoRequest>(&mut dapi, filename)?
139 }
140 "GetDataContractRequest" => {
141 load_expectation::<proto::GetDataContractRequest>(&mut dapi, filename)?
142 }
143 "GetDataContractsRequest" => {
144 load_expectation::<proto::GetDataContractsRequest>(&mut dapi, filename)?
145 }
146 "GetDataContractHistoryRequest" => {
147 load_expectation::<proto::GetDataContractHistoryRequest>(&mut dapi, filename)?
148 }
149 "IdentityRequest" => load_expectation::<IdentityRequest>(&mut dapi, filename)?,
150 "GetIdentityRequest" => {
151 load_expectation::<proto::GetIdentityRequest>(&mut dapi, filename)?
152 }
153
154 "GetIdentityBalanceRequest" => {
155 load_expectation::<proto::GetIdentityBalanceRequest>(&mut dapi, filename)?
156 }
157 "GetIdentityContractNonceRequest" => {
158 load_expectation::<proto::GetIdentityContractNonceRequest>(&mut dapi, filename)?
159 }
160 "GetIdentityBalanceAndRevisionRequest" => load_expectation::<
161 proto::GetIdentityBalanceAndRevisionRequest,
162 >(&mut dapi, filename)?,
163 "GetAddressInfoRequest" => {
164 load_expectation::<proto::GetAddressInfoRequest>(&mut dapi, filename)?
165 }
166 "GetAddressesInfosRequest" => {
167 load_expectation::<proto::GetAddressesInfosRequest>(&mut dapi, filename)?
168 }
169 "GetIdentityKeysRequest" => {
170 load_expectation::<proto::GetIdentityKeysRequest>(&mut dapi, filename)?
171 }
172 "GetProtocolVersionUpgradeStateRequest" => load_expectation::<
173 proto::GetProtocolVersionUpgradeStateRequest,
174 >(&mut dapi, filename)?,
175 "GetProtocolVersionUpgradeVoteStatusRequest" => {
176 load_expectation::<proto::GetProtocolVersionUpgradeVoteStatusRequest>(
177 &mut dapi, filename,
178 )?
179 }
180 "GetContestedResourcesRequest" => {
181 load_expectation::<proto::GetContestedResourcesRequest>(&mut dapi, filename)?
182 }
183 "GetContestedResourceVoteStateRequest" => load_expectation::<
184 proto::GetContestedResourceVoteStateRequest,
185 >(&mut dapi, filename)?,
186 "GetContestedResourceVotersForIdentityRequest" => {
187 load_expectation::<proto::GetContestedResourceVotersForIdentityRequest>(
188 &mut dapi, filename,
189 )?
190 }
191 "GetContestedResourceIdentityVotesRequest" => {
192 load_expectation::<proto::GetContestedResourceIdentityVotesRequest>(
193 &mut dapi, filename,
194 )?
195 }
196 "GetVotePollsByEndDateRequest" => {
197 load_expectation::<proto::GetVotePollsByEndDateRequest>(&mut dapi, filename)?
198 }
199 "GetPrefundedSpecializedBalanceRequest" => load_expectation::<
200 proto::GetPrefundedSpecializedBalanceRequest,
201 >(&mut dapi, filename)?,
202 "GetPathElementsRequest" => {
203 load_expectation::<proto::GetPathElementsRequest>(&mut dapi, filename)?
204 }
205 "GetTotalCreditsInPlatformRequest" => load_expectation::<
206 proto::GetTotalCreditsInPlatformRequest,
207 >(&mut dapi, filename)?,
208 "GetIdentityTokenBalancesRequest" => {
209 load_expectation::<proto::GetIdentityTokenBalancesRequest>(&mut dapi, filename)?
210 }
211 "GetIdentitiesTokenBalancesRequest" => load_expectation::<
212 proto::GetIdentitiesTokenBalancesRequest,
213 >(&mut dapi, filename)?,
214 "GetIdentityTokenInfosRequest" => {
215 load_expectation::<proto::GetIdentityTokenInfosRequest>(&mut dapi, filename)?
216 }
217 "GetIdentitiesTokenInfosRequest" => {
218 load_expectation::<proto::GetIdentitiesTokenInfosRequest>(&mut dapi, filename)?
219 }
220 "GetTokenStatusesRequest" => {
221 load_expectation::<proto::GetTokenStatusesRequest>(&mut dapi, filename)?
222 }
223 "GetTokenTotalSupplyRequest" => {
224 load_expectation::<proto::GetTokenTotalSupplyRequest>(&mut dapi, filename)?
225 }
226 "GetGroupInfoRequest" => {
227 load_expectation::<proto::GetGroupInfoRequest>(&mut dapi, filename)?
228 }
229 "GetGroupInfosRequest" => {
230 load_expectation::<proto::GetGroupInfosRequest>(&mut dapi, filename)?
231 }
232 "GetGroupActionsRequest" => {
233 load_expectation::<proto::GetGroupActionsRequest>(&mut dapi, filename)?
234 }
235 "GetGroupActionSignersRequest" => {
236 load_expectation::<proto::GetGroupActionSignersRequest>(&mut dapi, filename)?
237 }
238 "EvoNode" => load_expectation::<EvoNode>(&mut dapi, filename)?,
239 "GetTokenDirectPurchasePricesRequest" => load_expectation::<
240 proto::GetTokenDirectPurchasePricesRequest,
241 >(&mut dapi, filename)?,
242 "GetTokenPerpetualDistributionLastClaimRequest" => {
243 load_expectation::<proto::GetTokenPerpetualDistributionLastClaimRequest>(
244 &mut dapi, filename,
245 )?
246 }
247 "GetTokenPreProgrammedDistributionsRequest" => {
248 load_expectation::<proto::GetTokenPreProgrammedDistributionsRequest>(
249 &mut dapi, filename,
250 )?
251 }
252 "GetAddressesTrunkStateRequest" => {
253 load_expectation::<proto::GetAddressesTrunkStateRequest>(&mut dapi, filename)?
254 }
255 _ => {
256 return Err(Error::Config(format!(
257 "unknown request type {} in {}, missing match arm in load_expectations?",
258 request_type,
259 filename.display()
260 )))
261 }
262 };
263 }
264
265 Ok(self)
266 }
267
268 pub async fn expect_fetch<O: Fetch + MockResponse, Q: Query<<O as Fetch>::Request>>(
319 &mut self,
320 query: Q,
321 object: Option<O>,
322 ) -> Result<&mut Self, Error>
323 where
324 <<O as Fetch>::Request as TransportRequest>::Response: Default,
325 {
326 let grpc_request = query.query(self.prove()).expect("query must be correct");
327 self.expect(grpc_request, object).await?;
328
329 Ok(self)
330 }
331
332 pub async fn remove_fetch_expectation<O, Q>(&mut self, query: Q) -> bool
336 where
337 O: Fetch,
338 Q: Query<<O as Fetch>::Request>,
339 <O as Fetch>::Request: TransportRequest,
340 {
341 let grpc_request = query.query(self.prove()).expect("query must be correct");
342 self.remove(grpc_request).await
343 }
344
345 pub async fn expect_fetch_many<
376 K: Ord,
377 O: FetchMany<K, R>,
378 Q: Query<<O as FetchMany<K, R>>::Request>,
379 R,
380 >(
381 &mut self,
382 query: Q,
383 objects: Option<R>,
384 ) -> Result<&mut Self, Error>
385 where
386 R: FromIterator<(K, Option<O>)>
387 + MockResponse
388 + FromProof<
389 <O as FetchMany<K, R>>::Request,
390 Request = <O as FetchMany<K, R>>::Request,
391 Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
392 > + Sync
393 + Send
394 + Default,
395 <<O as FetchMany<K, R>>::Request as TransportRequest>::Response: Default,
396 {
397 let grpc_request = query.query(self.prove()).expect("query must be correct");
398 self.expect(grpc_request, objects).await?;
399
400 Ok(self)
401 }
402
403 async fn expect<I: TransportRequest, O: MockResponse>(
405 &mut self,
406 grpc_request: I,
407 returned_object: Option<O>,
408 ) -> Result<(), Error>
409 where
410 I::Response: Default,
411 {
412 let key = Key::new(&grpc_request);
413
414 if self.from_proof_expectations.contains_key(&key) {
416 return Err(MockError::MockExpectationConflict(format!(
417 "proof expectation key {} already defined for {} request: {:?}",
418 key,
419 std::any::type_name::<I>(),
420 grpc_request
421 ))
422 .into());
423 }
424
425 self.from_proof_expectations
427 .insert(key, returned_object.mock_serialize(self));
428
429 let mut dapi_guard = self.dapi.lock().await;
431 dapi_guard.expect(
433 &grpc_request,
434 &Ok(ExecutionResponse {
435 inner: Default::default(),
436 retries: 0,
437 address: "http://127.0.0.1".parse().expect("failed to parse address"),
438 }),
439 )?;
440
441 Ok(())
442 }
443
444 async fn remove<I: TransportRequest>(&mut self, grpc_request: I) -> bool {
446 let key = Key::new(&grpc_request);
447 let removed_from_proof = self.from_proof_expectations.remove(&key).is_some();
448
449 let mut dapi_guard = self.dapi.lock().await;
450 let removed_from_dapi = dapi_guard.remove(&grpc_request);
451
452 removed_from_proof || removed_from_dapi
453 }
454
455 pub(crate) fn parse_proof_with_metadata<I, O: FromProof<I>>(
457 &self,
458 request: O::Request,
459 response: O::Response,
460 ) -> Result<(Option<O>, ResponseMetadata, Proof), drive_proof_verifier::Error>
461 where
462 O::Request: Mockable,
463 Option<O>: MockResponse,
464 {
466 let key = Key::new(&request);
467
468 let data = match self.from_proof_expectations.get(&key) {
469 Some(d) => (
470 Option::<O>::mock_deserialize(self, d),
471 ResponseMetadata::default(),
472 Proof::default(),
473 ),
474 None => {
475 let version = self.version();
476 let provider = self.context_provider()
477 .ok_or(ContextProviderError::InvalidQuorum(
478 "expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
479 ))?;
480 O::maybe_from_proof_with_metadata(
481 request,
482 response,
483 Network::Regtest,
484 version,
485 &provider,
486 )?
487 }
488 };
489
490 Ok(data)
491 }
492 fn context_provider(&self) -> Option<impl ContextProvider> {
494 if let Some(sdk) = self.sdk.load_full() {
495 sdk.clone().context_provider()
496 } else {
497 None
498 }
499 }
500}
501
502fn load_expectation<T: TransportRequest>(
508 dapi_guard: &mut OwnedMutexGuard<MockDapiClient>,
509 path: &PathBuf,
510) -> Result<(), Error> {
511 let data = DumpData::<T>::load(path)
512 .map_err(|e| {
513 Error::Config(format!(
514 "cannot load mock expectations from {}: {}",
515 path.display(),
516 e
517 ))
518 })?
519 .deserialize();
520 dapi_guard.expect(&data.0, &data.1)?;
521 Ok(())
522}