astarte_device_sdk_mock/
lib.rs

1// This file is part of Astarte.
2//
3// Copyright 2024 - 2025 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19use std::{future::Future, path::Path};
20
21use astarte_device_sdk::aggregate::AstarteObject;
22use astarte_device_sdk::astarte_interfaces::Interface;
23use astarte_device_sdk::client::{ClientDisconnect, RecvError};
24use astarte_device_sdk::properties::PropAccess;
25use astarte_device_sdk::store::StoredProp;
26use astarte_device_sdk::transport::Connection;
27use astarte_device_sdk::{AstarteData, DeviceEvent, Error};
28use mockall::mock;
29
30// Export public facing dependencies
31pub use mockall;
32
33// FIXME: remove, still present for backwards compatibility
34pub use astarte_device_sdk::Client;
35
36pub trait DeviceIntrospection {
37    fn get_interface<F, O>(&self, interface_name: &str, f: F) -> impl Future<Output = O> + Send
38    where
39        F: FnMut(Option<&Interface>) -> O + Send + 'static,
40        O: 'static;
41}
42
43pub trait DynamicIntrospection {
44    fn add_interface(
45        &mut self,
46        interface: Interface,
47    ) -> impl Future<Output = Result<bool, Error>> + Send;
48
49    fn extend_interfaces<I>(
50        &mut self,
51        interfaces: I,
52    ) -> impl Future<Output = Result<Vec<String>, Error>> + Send
53    where
54        I: IntoIterator<Item = Interface> + Send + 'static;
55
56    fn add_interface_from_file<P>(
57        &mut self,
58        file_path: P,
59    ) -> impl Future<Output = Result<bool, Error>> + Send
60    where
61        P: AsRef<Path> + Send + Sync + 'static;
62
63    fn add_interface_from_str(
64        &mut self,
65        json_str: &str,
66    ) -> impl Future<Output = Result<bool, Error>> + Send;
67
68    fn remove_interface(
69        &mut self,
70        interface_name: &str,
71    ) -> impl Future<Output = Result<bool, Error>> + Send;
72
73    fn remove_interfaces<I>(
74        &mut self,
75        interfaces_name: I,
76    ) -> impl Future<Output = Result<Vec<String>, Error>> + Send
77    where
78        I: IntoIterator<Item = String> + Send + 'static,
79        I::IntoIter: Send;
80}
81
82mock! {
83    pub DeviceClient<C: Connection + 'static> { }
84
85    impl<C: Connection> Clone for DeviceClient<C> {
86        fn clone(&self) -> Self;
87    }
88
89    impl<C: Connection> Client for DeviceClient<C> {
90        async fn send_object_with_timestamp(
91            &mut self,
92            interface_name: &str,
93            interface_path: &str,
94            data: AstarteObject,
95            timestamp: chrono::DateTime<chrono::Utc>,
96        ) -> Result<(), Error>;
97
98        async fn send_object(
99            &mut self,
100            interface_name: &str,
101            interface_path: &str,
102            data: AstarteObject,
103        ) -> Result<(), Error>;
104
105        async fn send_individual_with_timestamp(
106            &mut self,
107            interface_name: &str,
108            interface_path: &str,
109            data: AstarteData,
110            timestamp: chrono::DateTime<chrono::Utc>,
111        ) -> Result<(), Error>;
112
113        async fn send_individual(
114            &mut self,
115            interface_name: &str,
116            interface_path: &str,
117            data: AstarteData,
118        ) -> Result<(), Error>;
119
120        async fn set_property(
121            &mut self,
122            interface_name: &str,
123            interface_path: &str,
124            data: AstarteData
125        ) -> Result<(), Error>;
126
127        async fn unset_property(&mut self, interface_name: &str, interface_path: &str) -> Result<(), Error>;
128
129        async fn recv(&self) -> Result<DeviceEvent, RecvError>;
130    }
131
132    impl<C: Connection> DeviceIntrospection for DeviceClient<C> {
133        async fn get_interface<F, O>(&self, interface_name: &str, f: F) -> O
134        where
135            F: FnMut(Option<&Interface>) -> O + Send + 'static,
136            O: 'static;
137    }
138
139    impl<C: Connection> DynamicIntrospection for DeviceClient<C> {
140        async fn add_interface(&mut self, interface: Interface) -> Result<bool, Error>;
141
142        async fn extend_interfaces<I>(&mut self, interfaces: I) -> Result<Vec<String>, Error>
143        where
144            I: IntoIterator<Item = Interface> + Send + 'static;
145
146        async fn add_interface_from_file<P>(&mut self, file_path: P) -> Result<bool, Error>
147        where
148            P: AsRef<Path> + Send + Sync + 'static;
149
150        async fn add_interface_from_str(&mut self, json_str: &str) -> Result<bool, Error>;
151
152        async fn remove_interface(&mut self, interface_name: &str) -> Result<bool, Error>;
153
154        async fn remove_interfaces<I>(&mut self, interfaces_name: I) -> Result<Vec<String>, Error>
155            where
156                I: IntoIterator<Item = String> + Send + 'static,
157                I::IntoIter: Send;
158    }
159
160    impl<C: Connection> PropAccess for DeviceClient<C> {
161        async fn property(&self, interface: &str, path: &str) -> Result<Option<AstarteData>, Error>;
162        async fn interface_props(&self, interface: &str) -> Result<Vec<StoredProp>, Error>;
163        async fn all_props(&self) -> Result<Vec<StoredProp>, Error>;
164        async fn device_props(&self) -> Result<Vec<StoredProp>, Error>;
165        async fn server_props(&self) -> Result<Vec<StoredProp>, Error>;
166    }
167
168    impl<C: Connection> ClientDisconnect for DeviceClient<C> {
169        async fn disconnect(&mut self) -> Result<(), Error>;
170    }
171}
172
173mock! {
174    pub DeviceConnection<C: Connection + 'static> {}
175
176    impl<C: Connection> astarte_device_sdk::EventLoop for DeviceConnection<C> {
177        async fn handle_events(self) -> Result<(), crate::Error>;
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    /// Struct to keep the traits and mock consistent
186    #[derive(Debug, Clone)]
187    struct CheckMocks {}
188
189    impl astarte_device_sdk::Client for CheckMocks {
190        async fn send_object_with_timestamp(
191            &mut self,
192            _interface_name: &str,
193            _interface_path: &str,
194            _data: AstarteObject,
195            _timestamp: chrono::DateTime<chrono::Utc>,
196        ) -> Result<(), Error> {
197            Ok(())
198        }
199
200        async fn send_object(
201            &mut self,
202            _interface_name: &str,
203            _interface_path: &str,
204            _data: AstarteObject,
205        ) -> Result<(), Error> {
206            Ok(())
207        }
208
209        async fn send_individual_with_timestamp(
210            &mut self,
211            _interface_name: &str,
212            _interface_path: &str,
213            _data: AstarteData,
214            _timestamp: chrono::DateTime<chrono::Utc>,
215        ) -> Result<(), Error> {
216            Ok(())
217        }
218
219        async fn send_individual(
220            &mut self,
221            _interface_name: &str,
222            _interface_path: &str,
223            _data: AstarteData,
224        ) -> Result<(), Error> {
225            Ok(())
226        }
227
228        async fn set_property(
229            &mut self,
230            _interface_name: &str,
231            _mapping_path: &str,
232            _data: AstarteData,
233        ) -> Result<(), Error> {
234            Ok(())
235        }
236
237        async fn unset_property(
238            &mut self,
239            _interface_name: &str,
240            _mapping_path: &str,
241        ) -> Result<(), Error> {
242            Ok(())
243        }
244
245        async fn recv(&self) -> Result<DeviceEvent, RecvError> {
246            Ok(DeviceEvent {
247                interface: Default::default(),
248                path: Default::default(),
249                data: astarte_device_sdk::Value::Property(None),
250            })
251        }
252    }
253
254    impl DeviceIntrospection for CheckMocks {
255        async fn get_interface<F, O>(&self, interface_name: &str, f: F) -> O
256        where
257            F: FnMut(Option<&Interface>) -> O + Send + 'static,
258        {
259            astarte_device_sdk::introspection::DeviceIntrospection::get_interface(
260                self,
261                interface_name,
262                f,
263            )
264            .await
265        }
266    }
267
268    impl astarte_device_sdk::introspection::DeviceIntrospection for CheckMocks {
269        async fn get_interface<F, O>(&self, _interface_name: &str, mut f: F) -> O
270        where
271            F: FnMut(Option<&Interface>) -> O + Send,
272        {
273            f(None)
274        }
275    }
276
277    impl DynamicIntrospection for CheckMocks {
278        async fn add_interface(&mut self, interface: Interface) -> Result<bool, Error> {
279            astarte_device_sdk::introspection::DynamicIntrospection::add_interface(self, interface)
280                .await
281        }
282
283        async fn extend_interfaces<I>(&mut self, interfaces: I) -> Result<Vec<String>, Error>
284        where
285            I: IntoIterator<Item = Interface> + Send + 'static,
286        {
287            astarte_device_sdk::introspection::DynamicIntrospection::extend_interfaces(
288                self, interfaces,
289            )
290            .await
291        }
292
293        async fn add_interface_from_file<P>(&mut self, file_path: P) -> Result<bool, Error>
294        where
295            P: AsRef<Path> + Send + Sync + 'static,
296        {
297            astarte_device_sdk::introspection::DynamicIntrospection::add_interface_from_file(
298                self, file_path,
299            )
300            .await
301        }
302
303        async fn add_interface_from_str(&mut self, json_str: &str) -> Result<bool, Error> {
304            astarte_device_sdk::introspection::DynamicIntrospection::add_interface_from_str(
305                self, json_str,
306            )
307            .await
308        }
309
310        async fn remove_interface(&mut self, interface_name: &str) -> Result<bool, Error> {
311            astarte_device_sdk::introspection::DynamicIntrospection::remove_interface(
312                self,
313                interface_name,
314            )
315            .await
316        }
317
318        async fn remove_interfaces<I>(&mut self, interfaces_name: I) -> Result<Vec<String>, Error>
319        where
320            I: IntoIterator<Item = String> + Send + 'static,
321            I::IntoIter: Send,
322        {
323            astarte_device_sdk::introspection::DynamicIntrospection::remove_interfaces(
324                self,
325                interfaces_name,
326            )
327            .await
328        }
329    }
330
331    impl astarte_device_sdk::introspection::DynamicIntrospection for CheckMocks {
332        async fn add_interface(&mut self, _interface: Interface) -> Result<bool, Error> {
333            Ok(Default::default())
334        }
335
336        async fn extend_interfaces<I>(&mut self, _interfaces: I) -> Result<Vec<String>, Error>
337        where
338            I: IntoIterator<Item = Interface> + Send,
339        {
340            Ok(Default::default())
341        }
342
343        async fn add_interface_from_file<P>(&mut self, _file_path: P) -> Result<bool, Error>
344        where
345            P: AsRef<Path> + Send + Sync,
346        {
347            Ok(Default::default())
348        }
349
350        async fn add_interface_from_str(&mut self, _json_str: &str) -> Result<bool, Error> {
351            Ok(Default::default())
352        }
353
354        async fn remove_interface(&mut self, _interface_name: &str) -> Result<bool, Error> {
355            Ok(Default::default())
356        }
357
358        async fn remove_interfaces<I>(&mut self, _interfaces_name: I) -> Result<Vec<String>, Error>
359        where
360            I: IntoIterator<Item = String> + Send,
361            I::IntoIter: Send,
362        {
363            Ok(Default::default())
364        }
365    }
366
367    #[test]
368    fn should_construct() {
369        let _c = CheckMocks {};
370    }
371}