mirror of
https://codeberg.org/demostf/api-client.git
synced 2026-06-03 08:34:15 +02:00
only setup once
This commit is contained in:
parent
7860e8df34
commit
16637e31f5
3 changed files with 151 additions and 68 deletions
|
|
@ -11,7 +11,7 @@ readme = "README.md"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
reqwest = { version = "0.10", features = ["json"] }
|
reqwest = { version = "0.10", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
steamid-ng = "0.3"
|
steamid-ng = "0.3"
|
||||||
|
|
|
||||||
82
src/lib.rs
82
src/lib.rs
|
|
@ -1,11 +1,11 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use reqwest::{multipart, Client, IntoUrl, StatusCode, Url};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use std::fmt;
|
|
||||||
use reqwest::{Client, IntoUrl, Url, StatusCode, multipart};
|
|
||||||
use thiserror::Error;
|
|
||||||
use steamid_ng::SteamID;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use steamid_ng::SteamID;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
@ -76,7 +76,7 @@ impl UserRef {
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
UserRef::Id(id) => *id,
|
UserRef::Id(id) => *id,
|
||||||
UserRef::User(User { id, .. }) => *id
|
UserRef::User(User { id, .. }) => *id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ impl UserRef {
|
||||||
pub fn user(&self) -> Option<&User> {
|
pub fn user(&self) -> Option<&User> {
|
||||||
match self {
|
match self {
|
||||||
UserRef::Id(_) => None,
|
UserRef::Id(_) => None,
|
||||||
UserRef::User(ref user) => Some(user)
|
UserRef::User(ref user) => Some(user),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ impl UserRef {
|
||||||
pub async fn resolve<'a>(&'a self, client: &ApiClient) -> Result<Cow<'a, User>, Error> {
|
pub async fn resolve<'a>(&'a self, client: &ApiClient) -> Result<Cow<'a, User>, Error> {
|
||||||
match self {
|
match self {
|
||||||
UserRef::User(ref user) => Ok(Cow::Borrowed(user)),
|
UserRef::User(ref user) => Ok(Cow::Borrowed(user)),
|
||||||
UserRef::Id(id) => Ok(Cow::Owned(client.get_user(*id).await?))
|
UserRef::Id(id) => Ok(Cow::Owned(client.get_user(*id).await?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,8 +130,8 @@ struct NestedPlayerUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_nested_user<'de, D>(deserializer: D) -> Result<User, D::Error>
|
fn deserialize_nested_user<'de, D>(deserializer: D) -> Result<User, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let nested = NestedPlayerUser::deserialize(deserializer)?;
|
let nested = NestedPlayerUser::deserialize(deserializer)?;
|
||||||
Ok(User {
|
Ok(User {
|
||||||
|
|
@ -165,8 +165,8 @@ pub enum Class {
|
||||||
|
|
||||||
/// Deserializes a lowercase hex string to a `[u8; 16]`.
|
/// Deserializes a lowercase hex string to a `[u8; 16]`.
|
||||||
fn hex_to_digest<'de, D>(deserializer: D) -> Result<[u8; 16], D::Error>
|
fn hex_to_digest<'de, D>(deserializer: D) -> Result<[u8; 16], D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
use hex::FromHex;
|
use hex::FromHex;
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
|
|
@ -310,7 +310,9 @@ impl ApiClient {
|
||||||
|
|
||||||
let mut url = self.base_url.clone();
|
let mut url = self.base_url.clone();
|
||||||
url.set_path("/demos");
|
url.set_path("/demos");
|
||||||
Ok(self.client.get(url)
|
Ok(self
|
||||||
|
.client
|
||||||
|
.get(url)
|
||||||
.query(&[("page", page)])
|
.query(&[("page", page)])
|
||||||
.query(¶ms)
|
.query(¶ms)
|
||||||
.send()
|
.send()
|
||||||
|
|
@ -344,14 +346,9 @@ impl ApiClient {
|
||||||
pub async fn get(&self, demo_id: u32) -> Result<Demo, Error> {
|
pub async fn get(&self, demo_id: u32) -> Result<Demo, Error> {
|
||||||
let mut url = self.base_url.clone();
|
let mut url = self.base_url.clone();
|
||||||
url.set_path(&format!("/demos/{}", demo_id));
|
url.set_path(&format!("/demos/{}", demo_id));
|
||||||
Ok(self.client.get(url)
|
Ok(self.client.get(url).send().await?.json().await?)
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get user info by id
|
/// Get user info by id
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|
@ -372,11 +369,7 @@ impl ApiClient {
|
||||||
pub async fn get_user(&self, user_id: u32) -> Result<User, Error> {
|
pub async fn get_user(&self, user_id: u32) -> Result<User, Error> {
|
||||||
let mut url = self.base_url.clone();
|
let mut url = self.base_url.clone();
|
||||||
url.set_path(&format!("/users/{}", user_id));
|
url.set_path(&format!("/users/{}", user_id));
|
||||||
Ok(self.client.get(url)
|
Ok(self.client.get(url).send().await?.json().await?)
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List demos with the provided options
|
/// List demos with the provided options
|
||||||
|
|
@ -401,24 +394,30 @@ impl ApiClient {
|
||||||
pub async fn get_chat(&self, demo_id: u32) -> Result<Vec<ChatMessage>, Error> {
|
pub async fn get_chat(&self, demo_id: u32) -> Result<Vec<ChatMessage>, Error> {
|
||||||
let mut url = self.base_url.clone();
|
let mut url = self.base_url.clone();
|
||||||
url.set_path(&format!("/demos/{}/chat", demo_id));
|
url.set_path(&format!("/demos/{}/chat", demo_id));
|
||||||
Ok(self.client.get(url)
|
Ok(self.client.get(url).send().await?.json().await?)
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_url(&self, demo_id: u32, backend: &str, path: &str, url: &str, hash: [u8; 16], key: &str) -> Result<(), Error> {
|
pub async fn set_url(
|
||||||
|
&self,
|
||||||
|
demo_id: u32,
|
||||||
|
backend: &str,
|
||||||
|
path: &str,
|
||||||
|
url: &str,
|
||||||
|
hash: [u8; 16],
|
||||||
|
key: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let mut api_url = self.base_url.clone();
|
let mut api_url = self.base_url.clone();
|
||||||
api_url.set_path(&format!("/demos/{}/url", demo_id));
|
api_url.set_path(&format!("/demos/{}/url", demo_id));
|
||||||
|
|
||||||
let respose = self.client.post(api_url)
|
let respose = self
|
||||||
|
.client
|
||||||
|
.post(api_url)
|
||||||
.form(&[
|
.form(&[
|
||||||
("hash", hex::encode(hash).as_str()),
|
("hash", hex::encode(hash).as_str()),
|
||||||
("backend", backend),
|
("backend", backend),
|
||||||
("url", url),
|
("url", url),
|
||||||
("path", path),
|
("path", path),
|
||||||
("key", key)
|
("key", key),
|
||||||
])
|
])
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -426,12 +425,21 @@ impl ApiClient {
|
||||||
match respose.status() {
|
match respose.status() {
|
||||||
StatusCode::UNAUTHORIZED => Err(Error::InvalidApiKey),
|
StatusCode::UNAUTHORIZED => Err(Error::InvalidApiKey),
|
||||||
StatusCode::PRECONDITION_FAILED => Err(Error::HashMisMatch),
|
StatusCode::PRECONDITION_FAILED => Err(Error::HashMisMatch),
|
||||||
_ if respose.status().is_server_error() => Err(Error::ServerError(respose.status().as_u16())),
|
_ if respose.status().is_server_error() => {
|
||||||
_ => Ok(())
|
Err(Error::ServerError(respose.status().as_u16()))
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upload_demo(&self, file_name: String, body: Vec<u8>, red: String, blue: String, key: String) -> Result<u32, Error> {
|
pub async fn upload_demo(
|
||||||
|
&self,
|
||||||
|
file_name: String,
|
||||||
|
body: Vec<u8>,
|
||||||
|
red: String,
|
||||||
|
blue: String,
|
||||||
|
key: String,
|
||||||
|
) -> Result<u32, Error> {
|
||||||
let form = multipart::Form::new()
|
let form = multipart::Form::new()
|
||||||
.text("red", red)
|
.text("red", red)
|
||||||
.text("blue", blue)
|
.text("blue", blue)
|
||||||
|
|
@ -444,7 +452,9 @@ impl ApiClient {
|
||||||
|
|
||||||
let form = form.part("demo", file);
|
let form = form.part("demo", file);
|
||||||
|
|
||||||
let resp = self.client.post(self.base_url.join("/upload").unwrap())
|
let resp = self
|
||||||
|
.client
|
||||||
|
.post(self.base_url.join("/upload").unwrap())
|
||||||
.multipart(form)
|
.multipart(form)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
|
|
@ -452,7 +462,7 @@ impl ApiClient {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if resp == "Invalid key" {
|
if resp == "Invalid key" {
|
||||||
return Err(Error::InvalidApiKey)
|
return Err(Error::InvalidApiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tail = resp.split('/').last().unwrap_or_default();
|
let tail = resp.split('/').last().unwrap_or_default();
|
||||||
|
|
|
||||||
133
tests/tests.rs
133
tests/tests.rs
|
|
@ -1,19 +1,45 @@
|
||||||
use demostf_client::{ApiClient, ListParams, ListOrder, Error};
|
use demostf_client::{ApiClient, Error, ListOrder, ListParams};
|
||||||
use steamid_ng::SteamID;
|
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use steamid_ng::SteamID;
|
||||||
|
|
||||||
|
static SETUP_DONE: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
async fn setup() {
|
||||||
|
if SETUP_DONE.swap(true, Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let db_url = std::env::var("DB_URL")
|
||||||
|
.unwrap_or_else(|_| "postgres://postgres:test@localhost:15432/postgres".to_string());
|
||||||
|
|
||||||
async fn test_client() -> ApiClient {
|
|
||||||
let pool = PgPoolOptions::new()
|
let pool = PgPoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect("postgres://postgres:test@localhost:15432/postgres").await.unwrap();
|
.connect(&db_url)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tables = ["chat", "demos", "kills", "players", "storage_keys", "teams", "upload_blacklist", "users"];
|
let tables = [
|
||||||
|
"chat",
|
||||||
|
"demos",
|
||||||
|
"kills",
|
||||||
|
"players",
|
||||||
|
"storage_keys",
|
||||||
|
"teams",
|
||||||
|
"upload_blacklist",
|
||||||
|
"users",
|
||||||
|
];
|
||||||
|
|
||||||
let mut transaction = pool.begin().await.unwrap();
|
let mut transaction = pool.begin().await.unwrap();
|
||||||
|
|
||||||
for table in &tables {
|
for table in &tables {
|
||||||
sqlx::query(&format!("TRUNCATE TABLE {}", table)).execute(&mut transaction).await.unwrap();
|
sqlx::query(&format!("TRUNCATE TABLE {}", table))
|
||||||
sqlx::query(&format!("ALTER SEQUENCE {}_id_seq RESTART with 1", table)).execute(&mut transaction).await.unwrap();
|
.execute(&mut transaction)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
sqlx::query(&format!("ALTER SEQUENCE {}_id_seq RESTART with 1", table))
|
||||||
|
.execute(&mut transaction)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlx::query("INSERT INTO users(steamid, name, avatar, token)\
|
sqlx::query("INSERT INTO users(steamid, name, avatar, token)\
|
||||||
|
|
@ -22,7 +48,20 @@ async fn test_client() -> ApiClient {
|
||||||
|
|
||||||
transaction.commit().await.unwrap();
|
transaction.commit().await.unwrap();
|
||||||
|
|
||||||
ApiClient::with_base_url("http://localhost:8888").unwrap()
|
let api_root =
|
||||||
|
std::env::var("API_ROOT").unwrap_or_else(|_| "http://localhost:8888".to_string());
|
||||||
|
|
||||||
|
let client = ApiClient::with_base_url(&api_root).unwrap();
|
||||||
|
|
||||||
|
upload(&client, "./tests/data/gully.dem", "test.dem", "R", "B").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_client() -> ApiClient {
|
||||||
|
setup().await;
|
||||||
|
let api_root =
|
||||||
|
std::env::var("API_ROOT").unwrap_or_else(|_| "http://localhost:8888".to_string());
|
||||||
|
|
||||||
|
ApiClient::with_base_url(&api_root).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -36,7 +75,16 @@ async fn test_get_user() {
|
||||||
async fn upload(client: &ApiClient, source: &str, name: &str, red: &str, blue: &str) -> u32 {
|
async fn upload(client: &ApiClient, source: &str, name: &str, red: &str, blue: &str) -> u32 {
|
||||||
let data = std::fs::read(source).unwrap();
|
let data = std::fs::read(source).unwrap();
|
||||||
|
|
||||||
client.upload_demo(name.to_string(), data, red.to_string(), blue.to_string(), "test_token".to_string()).await.unwrap()
|
client
|
||||||
|
.upload_demo(
|
||||||
|
name.to_string(),
|
||||||
|
data,
|
||||||
|
red.to_string(),
|
||||||
|
blue.to_string(),
|
||||||
|
"test_token".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -45,7 +93,16 @@ async fn test_upload_invalid_key() {
|
||||||
|
|
||||||
let data = std::fs::read("./tests/data/gully.dem").unwrap();
|
let data = std::fs::read("./tests/data/gully.dem").unwrap();
|
||||||
|
|
||||||
let err = client.upload_demo("name.dem".to_string(), data, "red".to_string(), "blue".to_string(), "wrong_token".to_string()).await.unwrap_err();
|
let err = client
|
||||||
|
.upload_demo(
|
||||||
|
"name.dem".to_string(),
|
||||||
|
data,
|
||||||
|
"red".to_string(),
|
||||||
|
"blue".to_string(),
|
||||||
|
"wrong_token".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
assert!(matches!(err, Error::InvalidApiKey));
|
assert!(matches!(err, Error::InvalidApiKey));
|
||||||
}
|
}
|
||||||
|
|
@ -54,16 +111,17 @@ async fn test_upload_invalid_key() {
|
||||||
async fn test_list_demos() {
|
async fn test_list_demos() {
|
||||||
let client = test_client().await;
|
let client = test_client().await;
|
||||||
|
|
||||||
|
let demos = client
|
||||||
let id = upload(&client, "./tests/data/gully.dem", "test.dem", "R", "B").await;
|
.list(ListParams::default().with_order(ListOrder::Ascending), 1)
|
||||||
|
.await
|
||||||
assert_eq!(1, id);
|
.unwrap();
|
||||||
|
|
||||||
let demos = client.list(ListParams::default().with_order(ListOrder::Ascending), 1).await.unwrap();
|
|
||||||
assert_eq!(demos[0].id, 1);
|
assert_eq!(demos[0].id, 1);
|
||||||
assert_eq!(demos[0].uploader.id(), 1);
|
assert_eq!(demos[0].uploader.id(), 1);
|
||||||
assert!(demos[0].uploader.user().is_none());
|
assert!(demos[0].uploader.user().is_none());
|
||||||
assert_eq!(demos[0].uploader.resolve(&client).await.unwrap().steam_id, SteamID::from(76561198024494988));
|
assert_eq!(
|
||||||
|
demos[0].uploader.resolve(&client).await.unwrap().steam_id,
|
||||||
|
SteamID::from(76561198024494988)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(demos[0].player_count, 12);
|
assert_eq!(demos[0].player_count, 12);
|
||||||
assert_eq!(demos[0].name, "test.dem");
|
assert_eq!(demos[0].name, "test.dem");
|
||||||
|
|
@ -75,14 +133,21 @@ async fn test_list_demos() {
|
||||||
async fn test_get_demo() {
|
async fn test_get_demo() {
|
||||||
let client = test_client().await;
|
let client = test_client().await;
|
||||||
|
|
||||||
let id = upload(&client, "./tests/data/gully.dem", "test.dem", "R", "B").await;
|
let demo = client.get(1).await.unwrap();
|
||||||
|
assert_eq!(demo.id, 1);
|
||||||
let demo = client.get(id).await.unwrap();
|
|
||||||
assert_eq!(demo.id, id);
|
|
||||||
assert_eq!(demo.uploader.id(), 1);
|
assert_eq!(demo.uploader.id(), 1);
|
||||||
assert!(demo.uploader.user().is_some());
|
assert!(demo.uploader.user().is_some());
|
||||||
assert_eq!(demo.uploader.user().unwrap().steam_id, SteamID::from(76561198024494988));
|
assert_eq!(
|
||||||
assert_eq!(demo.uploader.resolve(&client).await.unwrap().steam_id, SteamID::from(76561198024494988));
|
demo.uploader.user().unwrap().steam_id,
|
||||||
|
SteamID::from(76561198024494988)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
demo.uploader.resolve(&client).await.unwrap().steam_id,
|
||||||
|
SteamID::from(76561198024494988)
|
||||||
|
);
|
||||||
|
|
||||||
|
let players = &demo.players;
|
||||||
|
dbg!(players);
|
||||||
|
|
||||||
assert_eq!(demo.players[0].player_id, 1);
|
assert_eq!(demo.players[0].player_id, 1);
|
||||||
assert_eq!(demo.players[0].user.id, 2);
|
assert_eq!(demo.players[0].user.id, 2);
|
||||||
|
|
@ -93,8 +158,6 @@ async fn test_get_demo() {
|
||||||
async fn test_get_chat() {
|
async fn test_get_chat() {
|
||||||
let client = test_client().await;
|
let client = test_client().await;
|
||||||
|
|
||||||
upload(&client, "./tests/data/gully.dem", "test.dem", "R", "B").await;
|
|
||||||
|
|
||||||
let chat = client.get_chat(1).await.unwrap();
|
let chat = client.get_chat(1).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(chat.len(), 134);
|
assert_eq!(chat.len(), 134);
|
||||||
|
|
@ -108,9 +171,10 @@ async fn test_get_chat() {
|
||||||
async fn test_get_players() {
|
async fn test_get_players() {
|
||||||
let client = test_client().await;
|
let client = test_client().await;
|
||||||
|
|
||||||
upload(&client, "./tests/data/gully.dem", "test.dem", "R", "B").await;
|
let demos = client
|
||||||
|
.list(ListParams::default().with_order(ListOrder::Ascending), 1)
|
||||||
let demos = client.list(ListParams::default().with_order(ListOrder::Ascending), 1).await.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(demos[0].players.len(), 0);
|
assert_eq!(demos[0].players.len(), 0);
|
||||||
assert_eq!(demos[0].get_players(&client).await.unwrap().len(), 12);
|
assert_eq!(demos[0].get_players(&client).await.unwrap().len(), 12);
|
||||||
|
|
@ -120,6 +184,15 @@ async fn test_get_players() {
|
||||||
async fn test_set_url_invalid_key() {
|
async fn test_set_url_invalid_key() {
|
||||||
let client = test_client().await;
|
let client = test_client().await;
|
||||||
|
|
||||||
let res = client.set_url(9, "tests", "tests", "http://example.com/tests", [0; 16], "wrong").await;
|
let res = client
|
||||||
|
.set_url(
|
||||||
|
9,
|
||||||
|
"tests",
|
||||||
|
"tests",
|
||||||
|
"http://example.com/tests",
|
||||||
|
[0; 16],
|
||||||
|
"wrong",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(matches!(res.unwrap_err(), Error::InvalidApiKey));
|
assert!(matches!(res.unwrap_err(), Error::InvalidApiKey));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue