mirror of
https://codeberg.org/demostf/api-test.git
synced 2026-06-03 09:34:10 +02:00
init testing
This commit is contained in:
parent
a011c118b4
commit
679650369a
9 changed files with 2669 additions and 4 deletions
2
.env
Normal file
2
.env
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DB_URL=postgres://postgres:test@localhost:15432/postgres
|
||||
BASE_URL=http://localhost:8888
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
.env
|
||||
2309
Cargo.lock
generated
Normal file
2309
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
|
@ -4,6 +4,15 @@ version = "0.1.0"
|
|||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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
BIN
data/gully.dem
Normal file
Binary file not shown.
60
src/harness.rs
Normal file
60
src/harness.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
186
src/main.rs
186
src/main.rs
|
|
@ -1,3 +1,185 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
mod harness;
|
||||
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
86
src/report.rs
Normal 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
16
start_test_server.sh
Executable 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue