Skip to content

Commit 0adfd86

Browse files
committed
add integration test case
1 parent 9d3922c commit 0adfd86

11 files changed

+220
-26
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ build-spa-client-js:
1212
# make docker-release VERSION=1.2.3
1313
docker-release:
1414
ifeq ($(VERSION), )
15-
$(error VEDRSION is not set)
15+
$(error VERSION is not set)
1616
else
1717
DOCKER_BUILDKIT=1 docker build . -t="ghcr.io/fornetcode/spa-server:$(VERSION)"
1818
docker push ghcr.io/fornetcode/spa-server:$(VERSION)

client/src/api.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -197,28 +197,28 @@ mod test {
197197
let config = crate::config::test::default_local_config().unwrap();
198198
API::new(&config).unwrap()
199199
}
200-
#[test]
201-
fn get_domain_info() {
200+
#[tokio::test]
201+
async fn get_domain_info() {
202202
let api = get_api();
203-
let response = api.get_domain_info(None).unwrap();
203+
let response = api.get_domain_info(None).await.unwrap();
204204
println!("{:?}", response);
205205
}
206206

207207
#[tokio::test]
208208
async fn get_file_metadata() {
209209
let api = get_api();
210-
let r = api.get_file_metadata("self.noti.link", 1);
210+
let r = api.get_file_metadata("self.noti.link", 1).await;
211211
println!("{:?}", r);
212212
//api.upload_file("self.noti.link", &2.to_string(),PathBuf::new(""));
213213
}
214-
#[test]
215-
fn update_upload_status() {
214+
#[tokio::test]
215+
async fn update_upload_status() {
216216
let api = get_api();
217217
let r = api.change_uploading_status(UpdateUploadingStatusOption {
218218
domain: "www.baidu.com".to_owned(),
219219
version: 1,
220220
status: UploadingStatus::Finish,
221-
});
221+
}).await;
222222
println!("{:?}", r);
223223
}
224224
}

client/src/lib.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -77,45 +77,45 @@ mod test {
7777
env::set_var("SPA_UPLOAD_PARALLEL", "4");
7878
}
7979

80-
#[test]
81-
fn test_info() {
80+
#[tokio::test]
81+
async fn test_info() {
8282
init_config();
83-
run_with_commands(CliCommand::parse_from(&["test", "info"])).unwrap();
83+
run_with_commands(CliCommand::parse_from(&["test", "info"])).await.unwrap();
8484
}
8585

86-
#[test]
87-
fn test_upload() {
86+
#[tokio::test]
87+
async fn test_upload() {
8888
init_config();
8989
let ret = run_with_commands(CliCommand::parse_from(&[
9090
"test",
9191
"upload",
9292
"../example/js-app-example/build",
9393
"self.noti.link",
94-
]));
94+
])).await;
9595

9696
if let Err(ret) = ret {
9797
println!("{:?}", ret);
9898
}
9999
}
100-
#[test]
101-
fn test_release() {
100+
#[tokio::test]
101+
async fn test_release() {
102102
init_config();
103103
let result = run_with_commands(CliCommand::parse_from(&[
104104
"test",
105105
"release",
106106
"self.noti.link",
107-
]));
107+
])).await;
108108
result.unwrap();
109109
}
110-
#[test]
111-
fn test_delete() {
110+
#[tokio::test]
111+
async fn test_delete() {
112112
init_config();
113113
let result = run_with_commands(CliCommand::parse_from(&[
114114
"test",
115115
"delete",
116116
"self.noti.link",
117117
"2",
118-
]));
118+
])).await;
119119
result.unwrap();
120120
}
121121
}

client/src/upload_files.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ async fn retry_upload<T: Into<Cow<'static, str>> + Clone>(
168168
path: PathBuf,
169169
count: Arc<AtomicU64>,
170170
) -> Either<(String, u64), (String, u64)> {
171-
for retry in (0..3:u32).into_iter() {
171+
for retry in (0..3u32).into_iter() {
172172
let result = api
173173
.upload_file(domain.clone(), version.clone(), key.clone(), path.clone())
174174
.await;

docs/develop/roadmap.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
- doc: spa-client command help doc
77
- [x] integration check of files list
88
- **big break change**: config format change from honcon to yaml (honcon lib is archived)
9-
- support Let's Encrypt with [acme-lib](https://crates.io/crates/acme-lib)
9+
- support Let's Encrypt with [acme-lib](https://crates.io/crates/acme-lib)
10+
- admin server support HTTPS

tests/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ spa-client = { path = "../client" }
1212
spa-server = { path = "../server" }
1313
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std", "sync", "time", "tokio-macros", "test-util"] } # sync with spa-server
1414
reqwest = { version = "0.12", features = ["json", "multipart", "stream"], default-features = false } # from spa-client
15+
headers = "0.3.5" # from spa-server
1516
tracing = "0.1"
1617
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
1718
anyhow = "1.0.56"

tests/data/server_config.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ cache {
5252
// # gzip compression for js/json/icon/json, default is false,
5353
// # only support gzip algo, and only compress cached files,
5454
// # be careful to set it true
55-
compression = false
55+
compression = true
5656

5757
}
5858

tests/data/server_config_https.conf

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# http bind, if set port <= 0, will disable http server(need set https config)
2+
#port = 8080
3+
#addr = "0.0.0.0"
4+
5+
# directory to store static web files. if you use docker, please mount a persistence volume for it.
6+
file_dir = "./data/web"
7+
8+
# enable cors, default is false, its implementation is simple now.
9+
# Access-Control-Allow-Origin: $ORIGIN
10+
# Access-Control-Allow-Methods: OPTION,GET,HEAD
11+
# Access-Control-Max-Age: 3600
12+
cors = true
13+
14+
# https config, optional
15+
https {
16+
// # default value for https ssl
17+
ssl {
18+
# private ssl key
19+
private = "./data/cert/local.fornetcode.com.key",
20+
# public ssl cert
21+
public = "./data/cert/local.fornetcode.com.cert"
22+
}
23+
24+
// # https bind address
25+
port = 443
26+
addr = "0.0.0.0"
27+
28+
# if set true, http server(80) will send client
29+
# status code:301(Moved Permanently) to tell client redirect to https
30+
# optional, default is false
31+
http_redirect_to_https = true
32+
}
33+
34+
35+
36+
# default cache config
37+
cache {
38+
# if file size > max_size, it will not be cached. default is (10MB).
39+
max_size = 1kb
40+
41+
# http header Cache-Control config,
42+
# optional, if not set, won't sender this header to client
43+
client_cache = [{
44+
expire = 30d
45+
extension_names = [icon,gif,jpg,jpeg,png,js]
46+
}, {
47+
// set 0, would set Cache-Control: no-cache
48+
expire = 0
49+
extension_names = [html]
50+
}]
51+
52+
# gzip compression for js/json/icon/json, default is false,
53+
# only support gzip algo, and only compress cached files,
54+
# be careful to set it true
55+
compression = true
56+
57+
}
58+
59+
# admin server config
60+
# admin server don't support hot reload. the config should not change.
61+
# optional, and it's disabled by default.
62+
# if you use spa-client to upload files, control version. Need to open it
63+
admin_config {
64+
# bind host
65+
port = 9000
66+
addr = "127.0.0.1"
67+
68+
# this is used to check client request
69+
# put it in http header, Authorization: Bearer $token
70+
token = "token"
71+
72+
// # max file size allowed to be uploaded,
73+
// # default is 30MB(30*1024*1024)
74+
// max_upload_size = 31457280
75+
76+
// # delete deprecated version by cron
77+
// deprecated_version_delete {
78+
// # default value: every day at 3am.
79+
// cron: "0 0 3 * * *",
80+
// # default value is 2
81+
// max_preserve: 2,
82+
// }
83+
}
84+
85+
86+
# optional, domains specfic config, it will use the default config if not set
87+
domains = [{
88+
# domain name
89+
domain: "local.fornetcode.com",
90+
# optional, same with cache config, if not set, will use default cache config.
91+
// cache: {
92+
// client_cache:${cache.client_cache}
93+
// max_size: ${cache.max_size}
94+
// client_cache = ${cache.client_cache}
95+
// },
96+
# cors
97+
// cors: true,
98+
// # domain https config, if not set, will use default https config.
99+
}]

tests/tests/common.rs

+64-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
use reqwest::StatusCode;
2-
use std::path::PathBuf;
2+
use std::path::{Path, PathBuf};
33
use std::{env, fs, io};
4+
use std::time::Duration;
5+
use reqwest::header::CACHE_CONTROL;
6+
use headers::{CacheControl, Header, HeaderMapExt};
7+
8+
49
use tokio::task::JoinHandle;
510
use tracing::Level;
611
use tracing_subscriber::EnvFilter;
@@ -19,6 +24,10 @@ pub fn get_file_text(domain: &str, version: u32, path: &str) -> io::Result<Strin
1924
fs::read_to_string(path)
2025
}
2126

27+
pub fn get_server_data_path(domain:&str, version: u32) -> PathBuf {
28+
get_test_dir().join("web").join(domain).join(version.to_string())
29+
}
30+
2231
pub fn run_server() -> JoinHandle<()> {
2332
env::set_var(
2433
"SPA_CONFIG",
@@ -40,6 +49,14 @@ pub fn run_server() -> JoinHandle<()> {
4049
});
4150
}
4251

52+
53+
pub async fn reload_server() {
54+
let client_config =
55+
spa_client::config::Config::load(Some(get_test_dir().join("client_config.conf"))).unwrap();
56+
let client_api = spa_client::api::API::new(&client_config).unwrap();
57+
client_api.reload_spa_server().await.unwrap()
58+
}
59+
4360
pub async fn upload_file_and_check(
4461
domain: &str,
4562
request_prefix: &str,
@@ -102,9 +119,55 @@ pub async fn assert_files_no_exists(request_prefix: &str, check_path: Vec<&'stat
102119
);
103120
}
104121
}
122+
pub async fn assert_expired(request_prefix: &str, check_path: Vec<(&'static str, u64)>) {
123+
for (file, expired) in check_path {
124+
println!("begin to check: {request_prefix}/{file} expired");
125+
let result = reqwest::get(format!("{request_prefix}/{file}")).await.unwrap();
126+
127+
let mut cache_option = result.headers().get(CACHE_CONTROL).unwrap().clone();
128+
129+
let cache = CacheControl::decode(&mut cache_option).unwrap();
130+
131+
132+
let mut expect = CacheControl::new();
133+
if expired > 0 {
134+
expect = expect.with_max_age(Duration::from_secs(expired));
135+
} else {
136+
expect = expect.with_no_cache();
137+
}
138+
139+
assert_eq!(cache, expect);
140+
}
141+
142+
}
143+
144+
145+
105146
pub fn clean_test_dir(domain: &str) {
106147
let path = get_test_dir().join("web").join(domain);
107148
if path.exists() {
108149
fs::remove_dir_all(path).unwrap();
109150
}
110151
}
152+
153+
154+
pub fn copy_dir_all<P1:AsRef<Path>, P2:AsRef<Path>>(src: P1, dst: P2) -> io::Result<()> {
155+
let src = src.as_ref();
156+
let dst = dst.as_ref();
157+
if !dst.exists() {
158+
fs::create_dir(dst)?;
159+
}
160+
161+
for entry in fs::read_dir(src)? {
162+
let entry = entry?;
163+
let entry_path = entry.path();
164+
let dest_path = dst.join(entry.file_name());
165+
166+
if entry_path.is_dir() {
167+
copy_dir_all(&entry_path, &dest_path)?;
168+
} else {
169+
fs::copy(&entry_path, &dest_path)?;
170+
}
171+
}
172+
Ok(())
173+
}

tests/tests/starter.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::Duration;
22
mod common;
3-
use crate::common::{assert_files, assert_files_no_exists, clean_test_dir, upload_file_and_check};
3+
use crate::common::{assert_files, assert_files_no_exists, clean_test_dir, copy_dir_all, get_server_data_path, get_template_version, reload_server, upload_file_and_check};
44
use common::run_server;
55

66
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
@@ -42,7 +42,6 @@ async fn multiple_domain_check() {
4242

4343
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
4444
async fn evoke_cache_when_serving_new_index() {
45-
// this does not work correctly.
4645
clean_test_dir("self.noti.link");
4746
let domain = "self.noti.link/27";
4847
let request_prefix = "http://self.noti.link:8080/27";
@@ -76,3 +75,33 @@ async fn cool_start_server_and_serving_files() {
7675
assert_files(domain, request_prefix, 2, vec!["2.html"]).await;
7776
assert_files_no_exists(request_prefix, vec!["1.html"]).await;
7877
}
78+
79+
#[tokio::test]
80+
async fn simple_hot_reload() {
81+
clean_test_dir("self.noti.link");
82+
let domain = "self.noti.link/27";
83+
let request_prefix = "http://self.noti.link:8080/27";
84+
85+
run_server();
86+
tokio::time::sleep(Duration::from_secs(2)).await;
87+
upload_file_and_check(domain, request_prefix, 1, vec!["index.html", "1.html"]).await;
88+
let src_path = get_template_version(domain, 2);
89+
let dist_path = get_server_data_path(domain, 2);
90+
copy_dir_all(src_path, dist_path).unwrap();
91+
reload_server().await;
92+
93+
tokio::time::sleep(Duration::from_secs(1)).await;
94+
upload_file_and_check(domain, request_prefix, 2, vec!["index.html", "2.html"]).await;
95+
}
96+
97+
#[ignore]
98+
#[tokio::test]
99+
async fn self_signed_cert_https() {
100+
clean_test_dir("self.noti.link");
101+
let domain = "self.noti.link/27";
102+
let request_prefix = "https://self.noti.link/27";
103+
104+
run_server();
105+
tokio::time::sleep(Duration::from_secs(2)).await;
106+
upload_file_and_check(domain, request_prefix, 1, vec!["index.html", "1.html"]).await;
107+
}

0 commit comments

Comments
 (0)