Skip to main content

bpi_rs/audio/
status_number.rs

1//! 音频状态数
2//!
3//! [查看 API 文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/status_number.md)
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AudioStatusNumberData {
8    pub sid: i64,
9    pub play: i64,
10    pub collect: i64,
11    pub comment: i64,
12    pub share: i64,
13}
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18    use crate::audio::params::AudioSongParams;
19    use crate::ids::AudioId;
20    use crate::probe::contract::HttpMethod;
21    use crate::probe::endpoint_contract::EndpointContract;
22    use crate::{ApiEnvelope, BpiClient, BpiError, BpiResult};
23
24    const TEST_SID: u64 = 13603;
25
26    fn contract() -> BpiResult<EndpointContract> {
27        EndpointContract::from_slice(include_bytes!(
28            "../../tests/contracts/audio/status-number/contract.json"
29        ))
30    }
31
32    #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
33    #[tokio::test]
34    async fn test_audio_status_number() -> Result<(), Box<BpiError>> {
35        let bpi = BpiClient::new().expect("client should build");
36        let data = bpi
37            .audio()
38            .status_number(AudioSongParams::new(AudioId::new(TEST_SID)?))
39            .await?;
40        tracing::info!("{:#?}", data);
41
42        assert_eq!(data.sid, TEST_SID as i64);
43        assert!(data.play >= 0);
44        assert!(data.collect >= 0);
45        assert!(data.comment >= 0);
46        assert!(data.share >= 0);
47
48        Ok(())
49    }
50
51    #[test]
52    fn audio_status_number_contract_matches_endpoint_request() -> BpiResult<()> {
53        let contract = contract()?;
54        let params = AudioSongParams::new(AudioId::new(TEST_SID)?);
55
56        assert_eq!(contract.name, "audio.status_number");
57        assert_eq!(contract.request.method, HttpMethod::Get);
58        assert_eq!(
59            contract.request.url.as_str(),
60            "https://www.bilibili.com/audio/music-service-c/web/stat/song"
61        );
62        assert_eq!(
63            contract.request.query.get("sid").map(String::as_str),
64            Some("13603")
65        );
66        assert_eq!(params.query_pairs(), vec![("sid", "13603".to_string())]);
67        assert_eq!(contract.cases.len(), 3);
68        assert_eq!(
69            contract.cases[0].response.rust_model.as_deref(),
70            Some("AudioStatusNumberData")
71        );
72        Ok(())
73    }
74
75    #[test]
76    fn audio_status_number_response_fixtures_parse_declared_model() -> BpiResult<()> {
77        for bytes in [
78            include_bytes!(
79                "../../tests/contracts/audio/status-number/responses/anonymous.success.json"
80            )
81            .as_slice(),
82            include_bytes!(
83                "../../tests/contracts/audio/status-number/responses/normal.success.json"
84            )
85            .as_slice(),
86            include_bytes!("../../tests/contracts/audio/status-number/responses/vip.success.json")
87                .as_slice(),
88        ] {
89            let payload =
90                ApiEnvelope::<AudioStatusNumberData>::from_slice(bytes)?.into_payload()?;
91
92            assert_eq!(payload.sid, TEST_SID as i64);
93        }
94        Ok(())
95    }
96
97    fn local_probe_body(profile: &str) -> Option<serde_json::Value> {
98        let path = format!(
99            "target/bpi-probe-runs/audio/public-read/status-number/{profile}.response.json"
100        );
101        let bytes = std::fs::read(path).ok()?;
102        let value: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
103        value
104            .get("response")
105            .and_then(|response| response.get("body"))
106            .cloned()
107    }
108
109    #[test]
110    fn audio_status_number_model_matches_local_probe_outputs_when_available() -> BpiResult<()> {
111        for profile in ["anonymous", "normal", "vip"] {
112            let Some(body) = local_probe_body(profile) else {
113                continue;
114            };
115            let payload = serde_json::from_value::<ApiEnvelope<AudioStatusNumberData>>(body)?
116                .into_payload()?;
117
118            assert_eq!(payload.sid, TEST_SID as i64);
119        }
120        Ok(())
121    }
122}