Skip to main content

bpi_rs/web_widget/
banner.rs

1//! B站分区轮播图相关接口
2//!
3//! [查看 API 文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/web_widget/banner.html)
4use serde::{Deserialize, Serialize};
5
6/// 轮播图对象
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct RegionBanner {
9    pub image: String,     // 封面资源路径
10    pub title: String,     // 封面标题
11    pub sub_title: String, // 封面子标题
12    pub url: String,       // 点击后的跳转链接
13    pub rid: i64,          // 分区 ID
14}
15
16/// 轮播图响应数据
17#[derive(Debug, Clone, Deserialize, Serialize)]
18pub struct RegionBannerData {
19    pub region_banner_list: Vec<RegionBanner>, // 轮播图列表
20}
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25    use crate::probe::contract::HttpMethod;
26    use crate::probe::endpoint_contract::EndpointContract;
27    use crate::video::video_zone_v2::{Douga, VideoPartitionV2};
28    use crate::web_widget::params::WebWidgetRegionBannerParams;
29    use crate::{ApiEnvelope, BpiClient, BpiResult};
30
31    use tracing::info;
32
33    fn contract() -> BpiResult<EndpointContract> {
34        EndpointContract::from_slice(include_bytes!(
35            "../../tests/contracts/web_widget/region-banner/contract.json"
36        ))
37    }
38
39    #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
40    #[tokio::test]
41    async fn test_get_region_banner() {
42        let bpi = BpiClient::new().expect("client should build");
43        // 例如 region_id = 1 (动画)
44        let resp = bpi
45            .web_widget()
46            .region_banner(WebWidgetRegionBannerParams::new(VideoPartitionV2::Douga(
47                Douga::Douga,
48            )))
49            .await;
50        info!("响应: {:?}", resp);
51        assert!(resp.is_ok());
52
53        let data = resp.unwrap();
54        info!("分区轮播图: {:?}", data);
55    }
56
57    #[test]
58    fn web_widget_region_banner_contract_matches_endpoint_request() -> BpiResult<()> {
59        let contract = contract()?;
60        let partition = VideoPartitionV2::Douga(Douga::Douga);
61
62        assert_eq!(contract.name, "web_widget.region_banner");
63        assert_eq!(contract.request.method, HttpMethod::Get);
64        assert_eq!(
65            contract.request.url.as_str(),
66            "https://api.bilibili.com/x/web-show/region/banner"
67        );
68        assert_eq!(
69            contract.request.query.get("region_id").map(String::as_str),
70            Some(partition.tid().to_string().as_str())
71        );
72        assert_eq!(contract.cases.len(), 3);
73        assert_eq!(
74            contract.cases[0].response.rust_model.as_deref(),
75            Some("RegionBannerData")
76        );
77        Ok(())
78    }
79
80    #[test]
81    fn web_widget_region_banner_response_fixtures_parse_declared_model() -> BpiResult<()> {
82        for bytes in [
83            include_bytes!(
84                "../../tests/contracts/web_widget/region-banner/responses/anonymous.success.json"
85            )
86            .as_slice(),
87            include_bytes!(
88                "../../tests/contracts/web_widget/region-banner/responses/normal.success.json"
89            )
90            .as_slice(),
91            include_bytes!(
92                "../../tests/contracts/web_widget/region-banner/responses/vip.success.json"
93            )
94            .as_slice(),
95        ] {
96            let payload = ApiEnvelope::<RegionBannerData>::from_slice(bytes)?.into_payload()?;
97
98            assert!(!payload.region_banner_list.is_empty());
99        }
100        Ok(())
101    }
102
103    fn local_probe_body(profile: &str) -> Option<serde_json::Value> {
104        let path = format!(
105            "target/bpi-probe-runs/web_widget/public/region-banner/{profile}.response.json"
106        );
107        let bytes = std::fs::read(path).ok()?;
108        let value: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
109        value
110            .get("response")
111            .and_then(|response| response.get("body"))
112            .cloned()
113    }
114
115    #[test]
116    fn web_widget_region_banner_model_matches_local_probe_outputs_when_available() -> BpiResult<()>
117    {
118        for profile in ["anonymous", "normal", "vip"] {
119            let Some(body) = local_probe_body(profile) else {
120                continue;
121            };
122            let payload =
123                serde_json::from_value::<ApiEnvelope<RegionBannerData>>(body)?.into_payload()?;
124
125            assert!(!payload.region_banner_list.is_empty());
126        }
127        Ok(())
128    }
129}