support root and path deployments explicitly
This commit is contained in:
+103
-14
@@ -137,6 +137,7 @@ impl Config {
|
||||
} else {
|
||||
format!("/{trimmed_path}")
|
||||
};
|
||||
let base_url = base_url.trim_end_matches('/').to_string();
|
||||
|
||||
Ok(Self {
|
||||
base_url,
|
||||
@@ -1001,22 +1002,18 @@ mod tests {
|
||||
}
|
||||
|
||||
fn config(db_path: PathBuf) -> Config {
|
||||
Config {
|
||||
base_url: "https://example.test/s".into(),
|
||||
api_key: "secret".into(),
|
||||
host: "127.0.0.1".into(),
|
||||
port: 8080,
|
||||
config_for_base(db_path, "https://example.test/s")
|
||||
}
|
||||
|
||||
fn config_for_base(db_path: PathBuf, base_url: &str) -> Config {
|
||||
Config::from_raw(RawConfig {
|
||||
base_url: Some(base_url.into()),
|
||||
api_key: Some("secret".into()),
|
||||
db_path,
|
||||
retention_days: 0,
|
||||
min_short_length: 6,
|
||||
max_short_length: 32,
|
||||
max_url_length: 2048,
|
||||
max_retention_days: 3650,
|
||||
rate_limit_requests: 1_000,
|
||||
rate_limit_window: 60,
|
||||
production: false,
|
||||
base_path: "/s".into(),
|
||||
}
|
||||
..RawConfig::default()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn json(response: &ResponseData) -> Value {
|
||||
@@ -1032,6 +1029,98 @@ mod tests {
|
||||
assert_eq!(local_path("/api/health/", ""), Some("/api/health".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_endpoint_accepts_trailing_slashes_at_root_and_under_a_path() {
|
||||
for (label, base_url, prefix) in [
|
||||
("subdomain", "https://s.example.test/", ""),
|
||||
("subpath", "https://example.test/s/", "/s"),
|
||||
] {
|
||||
let path = temp_path(label);
|
||||
let app = App::new(config_for_base(path.clone(), base_url)).unwrap();
|
||||
let target = |route: &str| format!("{prefix}{route}");
|
||||
|
||||
assert_eq!(app.config.base_path, prefix);
|
||||
assert_eq!(app.handle(RequestData::new("GET", target("/"))).status, 200);
|
||||
assert_eq!(
|
||||
app.handle(RequestData::new("GET", target("///"))).status,
|
||||
200
|
||||
);
|
||||
assert_eq!(
|
||||
app.handle(RequestData::new("GET", target("/static/")))
|
||||
.status,
|
||||
200
|
||||
);
|
||||
assert_eq!(
|
||||
app.handle(RequestData::new("GET", target("/static/app.js/")))
|
||||
.status,
|
||||
200
|
||||
);
|
||||
assert_eq!(
|
||||
app.handle(RequestData::new("GET", target("/api/health/")))
|
||||
.status,
|
||||
200
|
||||
);
|
||||
assert_eq!(
|
||||
app.handle(RequestData::new("OPTIONS", target("/api/health/")))
|
||||
.status,
|
||||
200
|
||||
);
|
||||
|
||||
let mut create = RequestData::new("POST", target("/api/shorten/"));
|
||||
create.body = br#"{"url":"https://destination.example/one"}"#.to_vec();
|
||||
let created = app.handle(create);
|
||||
assert_eq!(created.status, 201);
|
||||
let short_url = String::from_utf8(created.body).unwrap();
|
||||
let expected_base = base_url.trim_end_matches('/');
|
||||
assert_eq!(app.config.base_url, expected_base);
|
||||
assert!(short_url.starts_with(&format!("{expected_base}/")));
|
||||
let code = short_url.rsplit('/').next().unwrap();
|
||||
|
||||
let get_create = app.handle(RequestData::new(
|
||||
"GET",
|
||||
target("/api/shorten/?url=https%3A%2F%2Fdestination.example%2Ftwo"),
|
||||
));
|
||||
assert_eq!(get_create.status, 201);
|
||||
|
||||
let lookup = app.handle(RequestData::new(
|
||||
"GET",
|
||||
target("/api/lookup/?url=https%3A%2F%2Fdestination.example%2Fone"),
|
||||
));
|
||||
assert_eq!(lookup.status, 200);
|
||||
assert_eq!(json(&lookup)["short_code"], code);
|
||||
assert_eq!(json(&lookup)["short_url"], short_url);
|
||||
|
||||
let metadata = app.handle(RequestData::new(
|
||||
"GET",
|
||||
target(&format!("/api/urls/{code}/")),
|
||||
));
|
||||
assert_eq!(metadata.status, 200);
|
||||
|
||||
let listing = app.handle(RequestData::new("GET", target("/api/urls/?api_key=secret")));
|
||||
assert_eq!(listing.status, 200);
|
||||
assert_eq!(json(&listing)["count"], 2);
|
||||
|
||||
let redirect = app.handle(RequestData::new("GET", target(&format!("/{code}/"))));
|
||||
assert_eq!(redirect.status, 302);
|
||||
assert!(redirect
|
||||
.headers
|
||||
.contains(&("Location".into(), "https://destination.example/one".into())));
|
||||
|
||||
let deleted = app.handle(RequestData::new(
|
||||
"DELETE",
|
||||
target(&format!("/api/urls/{code}/?api_key=secret")),
|
||||
));
|
||||
assert_eq!(deleted.status, 204);
|
||||
|
||||
let missing = app.handle(RequestData::new(
|
||||
"GET",
|
||||
target(&format!("/api/urls/{code}/")),
|
||||
));
|
||||
assert_eq!(missing.status, 404);
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_database_without_retention_column_is_migrated() {
|
||||
let path = temp_path("schema");
|
||||
|
||||
Reference in New Issue
Block a user