Skip to content

Commit

Permalink
test: add some tests & better ci
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
  • Loading branch information
explodingcamera committed Aug 14, 2024
1 parent b254937 commit 7aa31f6
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 16 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/audit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: "Audit Dependencies"
on:
push:
paths:
# Run if workflow changes
- ".github/workflows/audit.yml"
# Run on changed dependencies
- "**/Cargo.toml"
- "**/Cargo.lock"
# Run if the configuration file changes
- "**/audit.toml"
# Rerun periodicly to pick up new advisories
schedule:
- cron: "0 0 * * *"
# Run manually
workflow_dispatch:

jobs:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/audit@v1
name: Audit Rust Dependencies
24 changes: 24 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Run tests

on: [push, pull_request]

jobs:
test:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
name: Run tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test --all --all-features
- name: Run clippy
run: cargo clippy --all-features -- -W warnings
- name: Run fmt
run: cargo fmt --all -- --check
continue-on-error: true
17 changes: 13 additions & 4 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ refinery={version="0.8"}
refinery-core="0.8"
maxminddb={version="0.24", optional=true}

[dev-dependencies]
figment={version="0.10", features=["toml", "env", "test"]}

[features]
default=["geoip"]
geoip=["dep:maxminddb"]
Expand Down
79 changes: 78 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ impl Config {

let config: Config = Figment::new()
.merge(Toml::file(path.unwrap_or("liwan.config.toml".to_string())))
.merge(Env::prefixed("LIWAN_"))
.merge(Env::raw().filter_map(|key| match key {
k if !k.starts_with("LIWAN_") => None,
k if k.starts_with("LIWAN_MAXMIND_") => Some(format!("geoip.maxmind_{}", &k[14..]).into()),
k => Some(k[6..].as_str().to_lowercase().replace("_", ".").into()),
}))
.extract()?;

let url: Uri = Uri::from_str(&config.base_url).wrap_err("Invalid base URL")?;
Expand All @@ -63,3 +67,76 @@ impl Config {
self.base_url.starts_with("https")
}
}

#[cfg(test)]
mod test {
use super::*;
use figment::Jail;

#[test]
fn test_config() {
Jail::expect_with(|jail| {
jail.create_file(
"liwan2.config.toml",
r#"
base_url = "http://localhost:8081"
data_dir = "./liwan-test-data"
[geoip]
maxmind_db_path = "test2"
"#,
)?;

jail.set_env("LIWAN_MAXMIND_EDITION", "test");
jail.set_env("LIWAN_GEOIP_MAXMIND_EDITION", "test2");
jail.set_env("GEOIP_MAXMIND_EDITION", "test3");

jail.set_env("LIWAN_MAXMIND_LICENSE_KEY", "test");
jail.set_env("LIWAN_MAXMIND_ACCOUNT_ID", "test");
jail.set_env("LIWAN_MAXMIND_DB_PATH", "test");

let config = Config::load(Some("liwan2.config.toml".into())).expect("failed to load config");

assert_eq!(config.geoip.as_ref().unwrap().maxmind_edition, Some("test".to_string()));
assert_eq!(config.geoip.as_ref().unwrap().maxmind_license_key, Some("test".to_string()));
assert_eq!(config.geoip.as_ref().unwrap().maxmind_account_id, Some("test".to_string()));
assert_eq!(config.geoip.as_ref().unwrap().maxmind_db_path, Some("test".to_string()));
assert_eq!(config.base_url, "http://localhost:8081");
assert_eq!(config.data_dir, "./liwan-test-data");
assert_eq!(config.port, 8080);
Ok(())
});
}

#[test]
fn test_no_geoip() {
Jail::expect_with(|jail| {
jail.create_file(
"liwan3.config.toml",
r#"
base_url = "http://localhost:8081"
data_dir = "./liwan-test-data"
"#,
)?;

let config = Config::load(Some("liwan3.config.toml".into())).expect("failed to load config");

assert!(config.geoip.is_none());
assert_eq!(config.base_url, "http://localhost:8081");
assert_eq!(config.data_dir, "./liwan-test-data");
assert_eq!(config.port, 8080);
Ok(())
});
}

#[test]
fn test_no_config() {
Jail::expect_with(|_jail| {
let config = Config::load(None).expect("failed to load config");
assert!(config.geoip.is_none());
assert_eq!(config.base_url, "http://localhost:8080");
assert_eq!(config.data_dir, "./liwan-data");
assert_eq!(config.port, 8080);
Ok(())
});
}
}
24 changes: 14 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,12 @@ pub(crate) mod web;
use app::{models::Event, Liwan};
use config::Config;
use eyre::Result;
use tracing::Level;
use tracing_subscriber::EnvFilter;

#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let args = cli::args();

// external crates should use WARN
let filter = EnvFilter::from_default_env()
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), args.log_level).parse()?)
.add_directive(Level::WARN.into());
tracing_subscriber::fmt().with_env_filter(filter).compact().init();

#[cfg(debug_assertions)]
tracing::info!("Running in debug mode");
setup_logger(args.log_level)?;

let config = Config::load(args.config)?;
let (s, r) = crossbeam::channel::unbounded::<Event>();
Expand All @@ -38,3 +29,16 @@ async fn main() -> Result<()> {
res = tokio::task::spawn_blocking(move || app.clone().events.process(r)) => res?
}
}

fn setup_logger(log_level: tracing::Level) -> Result<()> {
// external crates should use WARN
let filter = EnvFilter::from_default_env()
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), log_level).parse()?)
.add_directive(tracing::Level::WARN.into());

tracing_subscriber::fmt().with_env_filter(filter).compact().init();

#[cfg(debug_assertions)]
tracing::info!("Running in debug mode");
Ok(())
}
28 changes: 28 additions & 0 deletions src/utils/referrer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,31 @@ pub(crate) fn process_referer(referer: Option<&str>) -> Result<Option<String>, (

Ok(res)
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_process_referer() {
assert_eq!(process_referer(None), Ok(None), "Should return None when no referer is provided");

assert_eq!(
process_referer(Some("https://example.com/path?query=string")),
Ok(Some("example.com".to_string())),
"Should return the FQDN for a valid referer that is not a spammer"
);

assert_eq!(
process_referer(Some("https://adf.ly/path")),
Err(()),
"Should return an error for a referer identified as a spammer"
);

assert_eq!(
process_referer(Some("invalid_referrer")),
Ok(Some("invalid_referrer".to_string())),
"Should return the original referrer if it is invalid"
);
}
}
15 changes: 15 additions & 0 deletions src/utils/useragent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,18 @@ static MOBILE_OS: [&str; 2] = ["iOS", "Android"]; // good enough for 99% of case
pub(crate) fn is_mobile(client: &Client) -> bool {
MOBILE_OS.contains(&&*client.os.family)
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_ua_parser() {
let user_agent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
let client = parse(user_agent);
assert_eq!(client.os.family, "iOS", "Expected OS family to be iOS");
assert_eq!(client.device.family, "iPhone", "Expected device family to be iPhone");
assert!(is_mobile(&client), "Expected device to be mobile");
assert!(!is_bot(&client), "Expected device to not be a bot");
}
}
41 changes: 41 additions & 0 deletions src/utils/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,44 @@ pub(crate) fn is_valid_username(name: &str) -> bool {
pub(crate) fn can_access_project(project: &Project, user: Option<&SessionUser>) -> bool {
project.public || user.map_or(false, |u| u.0.role == UserRole::Admin || u.0.projects.contains(&project.id))
}

#[cfg(test)]
mod tests {
use super::*;
use crate::app::models::{Project, User};

#[test]
fn test_can_access_project() {
let project = Project {
id: "public_project".to_string(),
display_name: "Public Project".to_string(),
secret: None,
public: true,
};
assert!(can_access_project(&project, None), "Public project should be accessible without a user.");

let user = SessionUser(User {
username: "test".to_string(),
role: UserRole::User,
projects: vec!["other".to_string()],
});
assert!(can_access_project(&project, Some(&user)), "Public project should be accessible with any user.");

let project = Project {
id: "admin_test_project".to_string(),
display_name: "Admin Test Project".to_string(),
secret: None,
public: false,
};
let admin_user = SessionUser(User { username: "admin".to_string(), role: UserRole::Admin, projects: vec![] });
assert!(can_access_project(&project, Some(&admin_user)), "Admin should have access to any project.");

let project = Project {
id: "private_project".to_string(),
display_name: "Private Project".to_string(),
secret: None,
public: false,
};
assert!(!can_access_project(&project, None), "Private project should not be accessible without a user.");
}
}
Binary file modified web/bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@uidotdev/usehooks": "^2.4.1",
"astro": "^4.13.3",
"astro": "^4.13.4",
"date-fns": "^3.6.0",
"fets": "^0.8.2",
"lightningcss": "^1.26.0",
Expand Down

0 comments on commit 7aa31f6

Please sign in to comment.