#![cfg(feature = "test_e2e")]
pub mod common;
use std::collections::HashMap;
use std::env::var;
use anyhow::Result;
use axum::{routing::IntoMakeService, Router, Server};
use deadpool_postgres::Config;
use freighter_api_types::index::request::{Publish, PublishDependency};
use freighter_api_types::index::IndexProvider;
use freighter_auth::pg_backend::PgAuthProvider;
use freighter_auth::AuthProvider;
use freighter_client::Client;
use freighter_fs_index::FsIndexProvider;
use freighter_pg_index::PgIndexProvider;
use freighter_server::ServiceConfig;
use freighter_storage::s3_client::S3StorageProvider;
use hyper::{server::conn::AddrIncoming, Body};
use semver::{Version, VersionReq};
use tracing_subscriber::util::SubscriberInitExt;
#[derive(Clone)]
struct TestServerConfig {
db: Config,
server_addr: String,
bucket_name: String,
bucket_endpoint_url: String,
bucket_access_key_id: String,
bucket_access_key_secret: String,
auth_required: bool,
}
impl TestServerConfig {
fn from_env(default_port: u16) -> TestServerConfig {
Self {
db: Config {
user: Some(var("POSTGRES_USER").unwrap_or("freighter".to_owned())),
password: Some(
var("POSTGRES_PASSWORD").unwrap_or("crates-crates-crates".to_owned()),
),
dbname: Some(var("POSTGRES_DBNAME").unwrap_or("freighter".to_owned())),
host: Some(var("POSTGRES_HOST").unwrap_or("localhost".to_owned())),
port: Some(
var("POSTGRES_PORT")
.map(|p| p.parse::<u16>().unwrap())
.unwrap_or(5432),
),
..Default::default()
},
server_addr: var("SERVER_ADDR").unwrap_or(format!("127.0.0.1:{default_port}")),
bucket_name: var("BUCKET_NAME").unwrap_or("crates".to_owned()),
bucket_endpoint_url: var("BUCKET_ENDPOINT")
.unwrap_or("http://127.0.0.1:9090".to_owned()),
bucket_access_key_id: var("BUCKET_ACCESS_KEY_ID").unwrap_or("1234567890".to_owned()),
bucket_access_key_secret: var("BUCKET_ACCESS_KEY_SECRET")
.unwrap_or("valid-secret".to_owned()),
auth_required: false,
}
}
}
fn server(
config: &TestServerConfig,
index_client: impl IndexProvider + Send + Sync + 'static,
auth_client: impl AuthProvider + Send + Sync + 'static,
) -> Result<Server<AddrIncoming, IntoMakeService<Router<(), Body>>>> {
let storage_client = S3StorageProvider::new(
&config.bucket_name,
&config.bucket_endpoint_url,
"us-east-1",
&config.bucket_access_key_id,
&config.bucket_access_key_secret,
);
let service = ServiceConfig {
address: config.server_addr.parse()?,
download_endpoint: format!("http://{}/downloads/", config.server_addr),
api_endpoint: format!("http://{}", config.server_addr.to_owned()),
metrics_address: "127.0.0.1:9999".parse()?,
allow_registration: true,
auth_required: config.auth_required,
};
let router = freighter_server::router(service, index_client, storage_client, auth_client);
Ok(axum::Server::bind(&config.server_addr.parse()?).serve(router.into_make_service()))
}
#[tokio::test]
async fn e2e_publish_crate_pg() {
let config = TestServerConfig::from_env(3000);
type ProviderConfig = <PgIndexProvider as IndexProvider>::Config;
e2e_publish_crate_in_index(
PgIndexProvider::new(ProviderConfig {
index_db: config.db.clone(),
})
.unwrap(),
config,
)
.await;
}
#[tokio::test]
async fn e2e_publish_crate_fs() {
let config = TestServerConfig::from_env(3001);
let dir = tempfile::tempdir().unwrap();
type ProviderConfig = <FsIndexProvider as IndexProvider>::Config;
let index_config = ProviderConfig::Path(dir.path().into());
e2e_publish_crate_in_index(FsIndexProvider::new(index_config).unwrap(), config).await;
}
#[tokio::test]
async fn e2e_publish_crate_fs_s3() {
let config = TestServerConfig::from_env(3002);
type ProviderConfig = <FsIndexProvider as IndexProvider>::Config;
let index_config = ProviderConfig::S3(freighter_fs_index::StoreConfig {
name: config.bucket_name.clone(),
endpoint_url: config.bucket_endpoint_url.clone(),
region: "us-east-1".into(),
access_key_id: Some(config.bucket_access_key_id.clone()),
access_key_secret: Some(config.bucket_access_key_secret.clone()),
});
e2e_publish_crate_in_index(FsIndexProvider::new(index_config).unwrap(), config).await;
}
#[tokio::test]
async fn e2e_publish_crate_fs_auth_required() {
let mut config = TestServerConfig::from_env(3003);
config.auth_required = true;
let dir = tempfile::tempdir().unwrap();
type ProviderConfig = <FsIndexProvider as IndexProvider>::Config;
let index_config = ProviderConfig::Path(dir.path().into());
e2e_publish_crate_in_index(FsIndexProvider::new(index_config).unwrap(), config).await;
}
async fn e2e_publish_crate_in_index(
index_client: impl IndexProvider + Send + Sync + 'static,
config: TestServerConfig,
) {
static ONE_AT_A_TIME: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
let _throttle = ONE_AT_A_TIME.lock().await;
let subscriber = tracing_subscriber::fmt().finish();
let _guard = subscriber.set_default();
let server_addr = config.server_addr.clone();
use rand::distributions::{Alphanumeric, DistString};
let test_unique_str = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
let crate_to_publish = format!("freighter-vegetables-{test_unique_str}");
let crate_to_publish_2 = format!("freighter-fruits-{test_unique_str}");
let client_username = format!("kargo-{test_unique_str}");
type AuthConfig = <PgAuthProvider as AuthProvider>::Config;
let auth_config = AuthConfig {
auth_db: config.db.clone(),
};
let auth_client = PgAuthProvider::new(auth_config).expect("Failed to initialize auth client");
let default_token = if config.auth_required {
Some(auth_client.register(&client_username).await.unwrap())
} else {
None
};
let server_spawned = tokio::spawn(server(&config, index_client, auth_client).unwrap());
let mut freighter_client =
Client::new(&format!("http://{server_addr}/index"), default_token).await;
if !config.auth_required {
freighter_client.register(&client_username).await.unwrap();
}
let tarball = [1u8; 100];
freighter_client
.publish(
&Publish {
name: crate_to_publish.clone(),
vers: Version::new(1, 2, 3),
deps: vec![PublishDependency {
name: "tokio".to_string(),
version_req: VersionReq::parse("1.0").unwrap(),
features: vec!["net".to_string(), "process".to_string(), "rt".to_string()],
optional: false,
default_features: false,
target: None,
kind: Default::default(),
registry: Some("https://github.com/rust-lang/crates.io-index".to_string()),
explicit_name_in_toml: None,
}],
features: HashMap::from_iter([("foo".to_string(), vec!["tokio/fs".to_string()])]),
authors: vec![],
description: None,
documentation: None,
homepage: None,
readme: None,
readme_file: None,
keywords: vec![],
categories: vec![],
license: None,
license_file: None,
repository: None,
badges: None,
links: None,
},
&tarball,
)
.await
.unwrap();
let publish_res = freighter_client
.publish(
&Publish {
name: crate_to_publish.clone(),
vers: Version::new(1, 2, 3),
deps: vec![PublishDependency {
name: "tokio".to_string(),
version_req: VersionReq::parse("1.0").unwrap(),
features: vec!["net".to_string(), "process".to_string(), "rt".to_string()],
optional: false,
default_features: false,
target: None,
kind: Default::default(),
registry: Some("https://github.com/rust-lang/crates.io-index".to_string()),
explicit_name_in_toml: None,
}],
features: HashMap::from_iter([("foo".to_string(), vec!["tokio/fs".to_string()])]),
authors: vec![],
description: None,
documentation: None,
homepage: None,
readme: None,
readme_file: None,
keywords: vec![],
categories: vec![],
license: None,
license_file: None,
repository: None,
badges: None,
links: None,
},
&tarball,
)
.await
.unwrap_err();
assert!(
matches!(publish_res, freighter_client::Error::Conflict),
"{:?}",
publish_res
);
freighter_client
.publish(
&Publish {
name: crate_to_publish_2.clone(),
vers: Version::new(2, 0, 0),
deps: vec![PublishDependency {
name: crate_to_publish.clone(),
version_req: VersionReq::parse("1.2").unwrap(),
features: vec!["foo".to_string()],
optional: false,
default_features: false,
target: None,
kind: Default::default(),
registry: None,
explicit_name_in_toml: None,
}],
features: HashMap::new(),
authors: vec![],
description: None,
documentation: None,
homepage: None,
readme: None,
readme_file: None,
keywords: vec![],
categories: vec![],
license: None,
license_file: None,
repository: None,
badges: None,
links: None,
},
&tarball,
)
.await
.unwrap();
let body = freighter_client
.download_crate(&crate_to_publish, &Version::new(1, 2, 3))
.await
.unwrap();
let index = freighter_client
.fetch_index(&crate_to_publish)
.await
.unwrap();
assert_eq!(index.len(), 1);
assert_eq!(body, &tarball[..]);
server_spawned.abort();
}