Skip to main content

hashiverse_lib/client/post_bundle/
stub_post_bundle_feedback_manager.rs

1//! # Test stub [`PostBundleFeedbackManager`]
2//!
3//! A do-nothing implementation of
4//! [`crate::client::post_bundle::post_bundle_feedback_manager::PostBundleFeedbackManager`]
5//! that always returns a "not implemented" error. Used by test scenarios that exercise
6//! timeline or posting logic without caring about feedback, so those tests don't need to
7//! stand up a full live feedback stack.
8
9use crate::client::post_bundle::post_bundle_feedback_manager::PostBundleFeedbackManager;
10use crate::protocol::posting::encoded_post_bundle_feedback::EncodedPostBundleFeedbackV1;
11use crate::tools::buckets::BucketLocation;
12use crate::tools::time::TimeMillis;
13
14#[derive(Default)]
15pub struct StubPostBundleFeedbackManager {
16}
17
18
19#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
20#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
21impl PostBundleFeedbackManager for StubPostBundleFeedbackManager {
22    async fn get_post_bundle_feedback(&self, _bucket_location: BucketLocation, _time_millis: TimeMillis) -> anyhow::Result<EncodedPostBundleFeedbackV1> {
23        anyhow::bail!("Not implemented");
24    }
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30    use bytes::{Bytes, BytesMut};
31    use crate::protocol::posting::encoded_post_feedback::{EncodedPostFeedbackV1, EncodedPostFeedbackViewV1};
32    use crate::tools::buckets::{BucketLocation, BucketType, BUCKET_DURATIONS};
33    use crate::tools::time::TimeMillis;
34    use crate::tools::types::{Id, Pow, Salt};
35
36    fn make_bucket_location() -> BucketLocation {
37        BucketLocation::new(BucketType::User, Id::random(), BUCKET_DURATIONS[0], TimeMillis(1_000_000)).unwrap()
38    }
39
40    #[tokio::test]
41    async fn stub_always_returns_error() {
42        let stub = StubPostBundleFeedbackManager::default();
43        let result = stub.get_post_bundle_feedback(make_bucket_location(), TimeMillis(1_000_000)).await;
44        assert!(result.is_err(), "stub should always return an error");
45        assert!(result.unwrap_err().to_string().contains("Not implemented"));
46    }
47
48    #[test]
49    fn feedback_roundtrip_encoding() {
50        let post_id = Id::random();
51        let feedback = EncodedPostFeedbackV1 {
52            post_id,
53            feedback_type: 1,
54            salt: Salt::random(),
55            pow: Pow(42),
56        };
57
58        let mut buf = BytesMut::new();
59        feedback.append_encode_to_bytes(&mut buf).unwrap();
60        let bytes = buf.freeze();
61
62        let view = EncodedPostFeedbackViewV1::iter(&bytes)
63            .next()
64            .expect("should yield one entry")
65            .expect("entry should decode without error");
66
67        assert_eq!(view.post_id_bytes(), post_id.as_ref());
68        assert_eq!(view.feedback_type(), 1);
69        assert_eq!(view.pow(), Pow(42));
70    }
71
72    #[test]
73    fn feedback_view_iter_multiple_entries() {
74        let post_ids: Vec<Id> = (0..3).map(|_| Id::random()).collect();
75        let mut combined = BytesMut::new();
76        for (i, &post_id) in post_ids.iter().enumerate() {
77            let feedback = EncodedPostFeedbackV1 {
78                post_id,
79                feedback_type: (i as u8) + 1,
80                salt: Salt::random(),
81                pow: Pow((i as u8) * 10),
82            };
83            feedback.append_encode_to_bytes(&mut combined).unwrap();
84        }
85        let bytes: Bytes = combined.freeze();
86
87        let views: Vec<_> = EncodedPostFeedbackViewV1::iter(&bytes)
88            .collect::<Result<Vec<_>, _>>()
89            .expect("all entries should decode");
90
91        assert_eq!(views.len(), 3);
92        for (i, view) in views.iter().enumerate() {
93            assert_eq!(view.post_id_bytes(), post_ids[i].as_ref());
94            assert_eq!(view.feedback_type(), (i as u8) + 1);
95            assert_eq!(view.pow(), Pow((i as u8) * 10));
96        }
97    }
98
99    #[test]
100    fn feedback_pow_comparison() {
101        // Feedback merge rule: for a given (post_id, feedback_type), keep the highest PoW.
102        // Verify the Pow type supports the comparisons needed for that rule.
103        assert!(Pow(10) > Pow(5));
104        assert!(Pow(0) < Pow(1));
105        assert_eq!(Pow(7), Pow(7));
106
107        // Verify that the "keep max" logic works with standard iterator helpers
108        let pows = vec![Pow(3), Pow(15), Pow(7)];
109        let strongest = pows.into_iter().max().unwrap();
110        assert_eq!(strongest, Pow(15));
111    }
112}