Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/openplotva-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ openplotva-web = { path = "../openplotva-web" }
futures-util.workspace = true
hex.workspace = true
image.workspace = true
quick-xml.workspace = true
quick-xml = { workspace = true, features = ["encoding"] }
rand.workspace = true
redis = { workspace = true, features = ["tokio-comp"] }
reqwest = { workspace = true, features = ["gzip"] }
Expand Down
43 changes: 34 additions & 9 deletions crates/openplotva-app/src/rates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,7 @@ impl MarketRatesClient {
let (body, stale) = self
.fetch_bytes(&self.urls.cbr_xml_url, MARKET_RATES_DAILY_TTL)
.await?;
let text =
String::from_utf8(body).map_err(|error| MarketRatesError::Decode(error.to_string()))?;
let parsed: CbrValCurs = quick_xml::de::from_str(&text)
let parsed: CbrValCurs = quick_xml::de::from_reader(body.as_slice())
.map_err(|error| MarketRatesError::Decode(error.to_string()))?;
parsed
.valutes
Expand Down Expand Up @@ -2421,6 +2419,27 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn market_rates_client_parses_windows_1251_cbr_xml()
-> Result<(), Box<dyn std::error::Error>> {
let xml = b"<?xml version=\"1.0\" encoding=\"windows-1251\"?><ValCurs Date=\"19.06.2026\"><Valute><CharCode>USD</CharCode><Nominal>1</Nominal><Name>\xC4\xEE\xEB\xEB\xE0\xF0</Name><Value>78,1234</Value></Valute></ValCurs>";
let (urls, server) = spawn_market_fixture_server(vec![FixtureResponse {
status: 200,
body: xml,
}])?;
let client = market_test_client(urls)?;
let snapshot = fetch_dialog_rates(&client, parse_rates_selection("USD/RUB")).await;
assert!(snapshot.errors.is_empty(), "{:?}", snapshot.errors);
assert_eq!(snapshot.rows[0].value, 78.1234);
assert_eq!(snapshot.rows[0].timestamp.as_deref(), Some("19.06.2026"));
assert_eq!(snapshot.rows[0].source, "cbr_cross");
let requests = server
.join()
.map_err(|_| io::Error::other("market fixture server panicked"))??;
assert_eq!(requests, vec!["/cbr".to_owned()]);
Ok(())
}

#[tokio::test]
async fn market_rates_client_fetches_cbr_cross_pairs() -> Result<(), Box<dyn std::error::Error>>
{
Expand Down Expand Up @@ -2684,16 +2703,22 @@ mod tests {
#[derive(Clone)]
struct FixtureResponse {
status: u16,
body: &'static str,
body: &'static [u8],
}

impl FixtureResponse {
fn ok(body: &'static str) -> Self {
Self { status: 200, body }
Self {
status: 200,
body: body.as_bytes(),
}
}

fn status(status: u16, body: &'static str) -> Self {
Self { status, body }
Self {
status,
body: body.as_bytes(),
}
}
}

Expand Down Expand Up @@ -2731,11 +2756,11 @@ mod tests {
);
write!(
stream,
"HTTP/1.1 {} OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
"HTTP/1.1 {} OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n",
response.status,
response.body.len(),
response.body
response.body.len()
)?;
stream.write_all(response.body)?;
}
Ok(requests)
});
Expand Down
Loading