Skip to main content

bpi_rs/web_widget/
zone_upload.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3
4/// 分区当日投稿稿件数信息
5/// 使用 `HashMap<u64, u64>` 存储,键为分区 ID,值为当日投稿数。
6#[derive(Debug, Clone, Deserialize)]
7pub struct OnlineRegionCount(pub HashMap<String, u64>);
8
9/// 分区当日投稿数数据
10#[derive(Debug, Clone, Deserialize)]
11pub struct OnlineData {
12    pub region_count: OnlineRegionCount,
13}
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18    use crate::probe::contract::HttpMethod;
19    use crate::probe::endpoint_contract::EndpointContract;
20    use crate::{ApiEnvelope, BpiClient, BpiResult};
21    use tracing::info;
22
23    fn contract() -> BpiResult<EndpointContract> {
24        EndpointContract::from_slice(include_bytes!(
25            "../../tests/contracts/web_widget/online/contract.json"
26        ))
27    }
28
29    #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
30    #[tokio::test]
31    async fn test_get_online() {
32        let bpi = BpiClient::new().expect("client should build");
33        let resp = bpi.web_widget().online().await;
34        info!("响应: {:?}", resp);
35        assert!(resp.is_ok());
36
37        if let Ok(data) = resp {
38            for (region_id, count) in data.region_count.0 {
39                info!("分区ID: {}, 投稿数: {}", region_id, count);
40            }
41        }
42    }
43
44    #[test]
45    fn web_widget_online_contract_matches_endpoint_request() -> BpiResult<()> {
46        let contract = contract()?;
47
48        assert_eq!(contract.name, "web_widget.online");
49        assert_eq!(contract.request.method, HttpMethod::Get);
50        assert_eq!(
51            contract.request.url.as_str(),
52            "https://api.bilibili.com/x/web-interface/online"
53        );
54        assert!(contract.request.query.is_empty());
55        assert_eq!(contract.cases.len(), 3);
56        assert_eq!(
57            contract.cases[0].response.rust_model.as_deref(),
58            Some("OnlineData")
59        );
60        Ok(())
61    }
62
63    #[test]
64    fn web_widget_online_response_fixtures_parse_declared_model() -> BpiResult<()> {
65        for bytes in [
66            include_bytes!(
67                "../../tests/contracts/web_widget/online/responses/anonymous.success.json"
68            )
69            .as_slice(),
70            include_bytes!("../../tests/contracts/web_widget/online/responses/normal.success.json")
71                .as_slice(),
72            include_bytes!("../../tests/contracts/web_widget/online/responses/vip.success.json")
73                .as_slice(),
74        ] {
75            let payload = ApiEnvelope::<OnlineData>::from_slice(bytes)?.into_payload()?;
76
77            assert!(!payload.region_count.0.is_empty());
78        }
79        Ok(())
80    }
81
82    fn local_probe_body(profile: &str) -> Option<serde_json::Value> {
83        let path =
84            format!("target/bpi-probe-runs/web_widget/public/online/{profile}.response.json");
85        let bytes = std::fs::read(path).ok()?;
86        let value: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
87        value
88            .get("response")
89            .and_then(|response| response.get("body"))
90            .cloned()
91    }
92
93    #[test]
94    fn web_widget_online_model_matches_local_probe_outputs_when_available() -> BpiResult<()> {
95        for profile in ["anonymous", "normal", "vip"] {
96            let Some(body) = local_probe_body(profile) else {
97                continue;
98            };
99            let payload =
100                serde_json::from_value::<ApiEnvelope<OnlineData>>(body)?.into_payload()?;
101
102            assert!(!payload.region_count.0.is_empty());
103        }
104        Ok(())
105    }
106}