only setup once

This commit is contained in:
Robin Appelman 2020-11-27 23:49:28 +01:00
commit 16637e31f5
3 changed files with 151 additions and 68 deletions

View file

@ -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"

View file

@ -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?)),
} }
} }
} }
@ -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(&params) .query(&params)
.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();

View file

@ -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));
} }