init testing

This commit is contained in:
Robin Appelman 2020-11-29 18:28:08 +01:00
commit 679650369a
9 changed files with 2669 additions and 4 deletions

2
.env Normal file
View file

@ -0,0 +1,2 @@
DB_URL=postgres://postgres:test@localhost:15432/postgres
BASE_URL=http://localhost:8888

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
.env

2309
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,15 @@ version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"] authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
demostf-client = { version = "0.1.2", default-features = false, features = ["rustls-tls"], path = "../api-client" }
sqlx = { version = "0.4", features = ["postgres", "runtime-tokio-rustls"] }
dotenv = "0.15.0"
color-eyre = "0.5.8"
colored = "2"
tokio = { version = "0.2", features = ["macros", "rt-core"] }
tf-demo-parser = { version = "0.1", path = "../tf-demo-parser" }
bitbuffer = "0.7.1"
[profile.dev.package.tf-demo-parser]
opt-level = 3

BIN
data/gully.dem Normal file

Binary file not shown.

60
src/harness.rs Normal file
View file

@ -0,0 +1,60 @@
use demostf_client::ApiClient;
use sqlx::{Pool, Postgres};
use color_eyre::Result;
use sqlx::postgres::PgPoolOptions;
pub struct Harness {
client: ApiClient,
db: Pool<Postgres>,
}
impl Harness {
pub async fn new(base_url: &str, db_url: &str) -> Result<Self> {
let client = ApiClient::with_base_url(base_url)?;
let db = PgPoolOptions::new()
.max_connections(5)
.connect(&db_url)
.await
.unwrap();
Ok(Harness {
client,
db,
})
}
pub async fn reset(&self) -> Result<()> {
let tables = [
"chat",
"demos",
"kills",
"players",
"storage_keys",
"teams",
"upload_blacklist",
"users",
];
let mut transaction = self.db.begin().await?;
for table in &tables {
sqlx::query(&format!("TRUNCATE TABLE {}", table))
.execute(&mut transaction)
.await?;
sqlx::query(&format!("ALTER SEQUENCE {}_id_seq RESTART with 1", table))
.execute(&mut transaction)
.await?;
}
sqlx::query("INSERT INTO users(steamid, name, avatar, token)\
VALUES(76561198024494988, 'Icewind', 'http://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/75/75b84075b70535c5cfb3499af03b3e4e7a7b556f_medium.jpg', 'token')")
.execute(&mut transaction).await?;
transaction.commit().await?;
Ok(())
}
pub fn client(&self) -> ApiClient {
self.client.clone()
}
}

View file

@ -1,3 +1,185 @@
fn main() { mod harness;
println!("Hello, world!"); mod report;
use crate::harness::Harness;
use bitbuffer::{BitReadBuffer, LittleEndian};
use color_eyre::{eyre::WrapErr, Report, Result};
use demostf_client::{Class, Demo, SteamID, Team};
use report::{assert_eq, Test};
use std::str::FromStr;
use tf_demo_parser::DemoParser;
macro_rules! assert_object_eq {
($obj:expr => { $($name:ident == $value:expr),* }) => {
$(report::assert_eq_borrow(&$obj.$name, $value)?;)*
};
($obj:expr => { $($name:ident == $value:expr),* , }) => {
$(report::assert_eq_borrow(&$obj.$name, $value)?;)*
};
}
#[tokio::main]
async fn main() -> Result<()> {
let harness = Harness::new(&dotenv::var("BASE_URL")?, &dotenv::var("DB_URL")?).await?;
Test::run(
"Upload demo, then retrieve info",
&harness,
|test| async move {
let id = test
.step("upload", |client| async move {
Ok(client
.upload_demo(
String::from("test.dem"),
std::fs::read("data/gully.dem")?,
String::from("RED"),
String::from("BLUE"),
String::from("token"),
)
.await?)
})
.await?;
assert_eq(id, 1)?;
test.step("get", |client| async move {
let demo = client.get(id).await?;
assert_object_eq!(demo => {
id == 1,
name == "test.dem",
map == "cp_gullywash_final1",
red_score == 5,
blue_score == 3,
player_count == 12,
});
verify_demo(&demo, std::fs::read("data/gully.dem")?)?;
assert_eq(demo.uploader.id(), 1)?;
let uploader = demo.uploader.resolve(client).await?;
assert_eq(&uploader.name, "Icewind")?;
Ok(())
})
.await?;
Ok(())
},
)
.await;
Ok(())
}
fn verify_demo(api_result: &Demo, demo: Vec<u8>) -> Result<()> {
use tf_demo_parser::demo::parser::gamestateanalyser;
fn map_team(team: Team) -> gamestateanalyser::Team {
match team {
Team::Red => gamestateanalyser::Team::Red,
Team::Blue => gamestateanalyser::Team::Blue,
}
}
fn map_class(class: Class) -> gamestateanalyser::Class {
match class {
Class::Scout => gamestateanalyser::Class::Scout,
Class::Soldier => gamestateanalyser::Class::Soldier,
Class::Pyro => gamestateanalyser::Class::Pyro,
Class::Demoman => gamestateanalyser::Class::Demoman,
Class::HeavyWeapons => gamestateanalyser::Class::Heavy,
Class::Medic => gamestateanalyser::Class::Medic,
Class::Engineer => gamestateanalyser::Class::Engineer,
Class::Sniper => gamestateanalyser::Class::Sniper,
Class::Spy => gamestateanalyser::Class::Spy,
}
}
let parser = DemoParser::new(BitReadBuffer::new(demo, LittleEndian).into());
let (header, state) = parser
.parse()
.map_err(|_| Report::msg("Failed to parse demo"))?;
assert_eq(&api_result.map, &header.map).wrap_err("Failed to compare map")?;
assert_eq(
api_result.red_score,
state
.rounds
.iter()
.filter(|round| round.winner == gamestateanalyser::Team::Red)
.count() as u8,
)
.wrap_err("Failed to compare red score")?;
assert_eq(
api_result.blue_score,
state
.rounds
.iter()
.filter(|round| round.winner == gamestateanalyser::Team::Blue)
.count() as u8,
)
.wrap_err("Failed to compare blue score")?;
let mut players = state
.users
.values()
.filter(|user| user.team.is_player())
.collect::<Vec<_>>();
players.sort_by(|a, b| {
SteamID::from_str(&a.steam_id)
.unwrap()
.account_id()
.cmp(&SteamID::from_str(&b.steam_id).unwrap().account_id())
});
let mut api_players = api_result.players.iter().collect::<Vec<_>>();
api_players.sort_by(|a, b| {
a.user
.steam_id
.account_id()
.cmp(&b.user.steam_id.account_id())
});
assert_eq(api_result.player_count, players.len() as u8)
.wrap_err("Failed to compare player count")?;
assert_eq(api_players.len(), players.len()).wrap_err("Failed to compare player count")?;
for (api_player, player) in api_players.iter().zip(players.iter()) {
assert_eq(&api_player.user.name, &player.name).wrap_err_with(|| {
format!("Failed to compare player name for {}", api_player.user.name)
})?;
assert_eq(
&api_player.user.steam_id,
&SteamID::from_str(&player.steam_id).unwrap(),
)
.wrap_err_with(|| format!("Failed to compare steam id for {}", api_player.user.name))?;
assert_eq(map_team(api_player.team), player.team)
.wrap_err_with(|| format!("Failed to compare team for {}", api_player.user.name))?;
assert_eq(
map_class(api_player.class),
player.classes.sorted().next().unwrap().0,
)
.wrap_err_with(|| format!("Failed to compare class for {}", api_player.user.name))?;
let kills = state
.deaths
.iter()
.filter(|kill| kill.killer == player.user_id)
.count() as u8;
let assists = state
.deaths
.iter()
.filter(|kill| kill.assister == Some(player.user_id))
.count() as u8;
let deaths = state
.deaths
.iter()
.filter(|kill| kill.victim == player.user_id)
.count() as u8;
assert_eq(api_player.kills, kills)
.wrap_err_with(|| format!("Failed to compare kills for {}", api_player.user.name))?;
assert_eq(api_player.assists, assists)
.wrap_err_with(|| format!("Failed to compare assists for {}", api_player.user.name))?;
assert_eq(api_player.deaths, deaths)
.wrap_err_with(|| format!("Failed to compare deaths for {}", api_player.user.name))?;
}
Ok(())
} }

86
src/report.rs Normal file
View file

@ -0,0 +1,86 @@
use crate::harness::Harness;
use color_eyre::{Report, Result};
use colored::Colorize;
use demostf_client::ApiClient;
use std::fmt::Debug;
use std::future::Future;
#[derive(Clone)]
pub struct Test {
client: ApiClient,
}
impl Test {
pub async fn run<'a, Fut: Future<Output = Result<()>> + 'a, F: FnOnce(Test) -> Fut + 'a>(
name: &str,
harness: &'a Harness,
f: F,
) {
println!(" - {}", name);
if let Err(e) = harness.reset().await {
println!(" {}: {:#}", "Reset api server".red(), e);
println!(" {}", "".red());
return;
} else {
println!(" {}", "Reset api server".green());
}
let test = Test {
client: harness.client(),
};
match f(test).await {
Ok(_) => {
println!(" {}", "".green());
}
Err(e) => {
println!(" {}: {:#}", "".red(), e);
}
}
}
pub async fn step<
'a,
T,
Fut: Future<Output = Result<T>> + 'a,
F: FnOnce(&'a ApiClient) -> Fut + 'a,
>(
&'a self,
name: &str,
f: F,
) -> Result<T> {
match f(&self.client).await {
Ok(res) => {
println!(" - {}", name.green());
Ok(res)
}
Err(e) => {
println!(" - {}: {:#}", name.red(), e);
Err(e)
}
}
}
}
pub fn assert_eq<A: Debug, B: PartialEq<A> + Debug>(a: A, b: B) -> Result<()> {
if b.eq(&a) {
Ok(())
} else {
Err(Report::msg(format!(
"Failed asserting that {:?} equals {:?}",
a, b
)))
}
}
pub fn assert_eq_borrow<A: Debug, B: PartialEq<A> + Debug>(a: &A, b: B) -> Result<()> {
if b.eq(a) {
Ok(())
} else {
Err(Report::msg(format!(
"Failed asserting that {:?} equals {:?}",
a, b
)))
}
}

16
start_test_server.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
docker rm -f api-test-db
docker rm -f api-test-fpm
docker rm -f api-test
mkdir -p /tmp/api-test-data
chmod 0777 /tmp/api-test-data
docker run -d --name api-test-db -e POSTGRES_PASSWORD=test -p 15432:5432 demostf/db
docker run -d --name api-test-fpm --link api-test-db:db -v /tmp/api-test-data:/demos \
-e DEMO_ROOT=/demos -e DEMO_HOST=localhost -e DB_TYPE=pgsql \
-e DB_HOST=db -e DB_PORT=5432 -e DB_DATABASE=postgres -e DB_USERNAME=postgres \
-e DB_PASSWORD=test -e APP_ROOT=http://api.localhost -e EDIT_SECRET=edit \
demostf/api
docker run -d --name api-test --link api-test-fpm:api -p 8888:80 demostf/api-nginx-test