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