Skip to content

Commit ef20b53

Browse files
authored
Merge pull request #113 from dfinity/igor/improve-tests
Add `ic-boundary` to integration tests
2 parents 8b97974 + b77ffd8 commit ef20b53

File tree

12 files changed

+185
-58
lines changed

12 files changed

+185
-58
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88

99
env:
1010
CARGO_TERM_COLOR: never
11+
GH_TOKEN: ${{ github.token }}
1112

1213
defaults:
1314
run:

run-tests.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#!/usr/bin/env bash
22
set -eEuo pipefail
33

4+
# Get the latest IC master hash that passed "CI Main".
5+
# This hash should have binaries published.
6+
readonly IC_COMMIT=$(gh run list --repo dfinity/ic --branch master --workflow "CI Main" --json headSha,status --jq '.[] | select(.status == "completed") | .headSha' | head -n 1)
7+
48
readonly POCKETIC_VERSION="9.0.1"
59
readonly POCKETIC_URL="https://github.com/dfinity/pocketic/releases/download/${POCKETIC_VERSION}/pocket-ic-x86_64-linux.gz"
610
readonly POCKETIC_CHECKSUM="237272216498074e5250a0685813b96632963ff9abbc51a7030d9b625985028d"
11+
readonly IC_BOUNDARY_URL="https://download.dfinity.systems/ic/${IC_COMMIT}/binaries/x86_64-linux/ic-boundary.gz"
712
readonly ASSET_WASM_URL="https://github.com/dfinity/sdk/raw/fec030f53814e7eaa2f869189e8852b5c0e60e5e/src/distributed/assetstorage.wasm.gz"
813
readonly ASSET_WASM_CHECKSUM="865eb25df5a6d857147e078bb33c727797957247f7af2635846d65c5397b36a6"
914
readonly LARGE_ASSETS_WASM_URL="https://github.com/dfinity/http-gateway/raw/42408f658199d7278d8ff3293504a06e1b0ef61d/examples/http-gateway/canister/http_gateway_canister_custom_assets.wasm.gz"
@@ -30,6 +35,11 @@ chmod +x "${POCKETIC_BIN}" || { log "Failed to make PocketIC executable"; exit 1
3035
export POCKET_IC_BIN="${POCKETIC_BIN}"
3136
log "PocketIC setup completed"
3237

38+
log "Downloading ic-boundary"
39+
curl -fsSL --retry 3 --retry-delay 5 "${IC_BOUNDARY_URL}" -o ic-boundary.gz
40+
gzip -d ic-boundary.gz
41+
chmod +x ic-boundary
42+
3343
log "Building ic-gateway"
3444
cargo build || { log "ic-gateway build failed"; exit 1; }
3545
export CARGO_TARGET_DIR
@@ -64,3 +74,5 @@ export ASSET_CANISTER_DIR="${CANISTER_DIR}"
6474
log "Running all tests"
6575
cargo test --all-features --profile dev --workspace -- --nocapture || { log "Tests failed"; exit 1; }
6676
log "All tests completed successfully"
77+
78+
rm -rf ic-boundary denylist_seed.json pocket-ic canister_wasms

src/routing/ic/handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub async fn handler(
6262
// HTTP2 lacks it, but canisters might expect it to be present.
6363
if parts.headers.get(HOST).is_none() {
6464
let host = ctx.authority.to_string();
65-
if let Ok(v) = HeaderValue::from_str(&host) {
65+
if let Ok(v) = HeaderValue::from_maybe_shared(Bytes::from(host)) {
6666
parts.headers.insert(HOST, v);
6767
}
6868
}

test_data/cert.pem

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIC6TCCAdGgAwIBAgIUK60AjMl8YTJ5nWViMweY043y6/EwDQYJKoZIhvcNAQEL
3+
BQAwDzENMAsGA1UEAwwEbm92ZzAeFw0yMzAxMDkyMTM5NTZaFw0zMzAxMDYyMTM5
4+
NTZaMA8xDTALBgNVBAMMBG5vdmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
5+
AoIBAQCd/7NXWeENaITmYU+eWMJEJMZa6v74g70RpZlprQzx148U0QOKEw/r6mmd
6+
SlbN4wsbb9lUu3zmXXpvYDAHYuOTYsDWcuNJXP/gCnPrD2wU8lJt3C5blmeU/9+0
7+
U6/ppRmu6kf/jmm7CMBnowI0+kdvTF7sbpiUBXTDujXNsqtX0FaksILc9ZAqpUCC
8+
2gqRcOXahzT2vnvJ2N+2bhveG+eB0/5oZcKgx0D4QgjR9k1+thWOQZUCJMg32OYS
9+
k4e57WhOQxu9Kh5N2MU1Ff3fhCYXzg7/GhJtWyDmjt1vNBwGW9Zn0BicySdcVFPC
10+
mRW3/rZrSpnwvsEnpIuyKGq+NMSXAgMBAAGjPTA7MAkGA1UdEwQCMAAwDwYDVR0R
11+
BAgwBoIEbm92ZzAdBgNVHQ4EFgQUYHN6l0ihbfbLQXqnKPltmv9DWDkwDQYJKoZI
12+
hvcNAQELBQADggEBAFBvyns/lJZ+zB4/Tmx3YUryji20XUNwhtlBC6V7rdWCXneY
13+
kqKVgbyDZ+XAYX2eL3o1gcv+XJxQgHfL+OqHJCVbK2kkYVSCW38WNVZb+oeTp/w3
14+
pgtmg91JcCjFEw2doqImLZLQDX6KK1gDGdTQ2dtisFcxGEkMUyjzqmZmZNzl+u7d
15+
JeDygLfGrMleO7ij2hP2vEfgkGbbvM+JCTav0B91Rj8/CbJHBwr8/CW4BJTjsqZC
16+
mglNb9+hY8N6XAxntoqZsFzuDyDx7ZSxeAW0yVRemrIPSgcPwpLDBFm4dCSwUHJN
17+
ujBjp7DRCQgg8uUq+0FMQ63ioZoR5mXQ5hzmTqk=
18+
-----END CERTIFICATE-----

test_data/key.pem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCd/7NXWeENaITm
3+
YU+eWMJEJMZa6v74g70RpZlprQzx148U0QOKEw/r6mmdSlbN4wsbb9lUu3zmXXpv
4+
YDAHYuOTYsDWcuNJXP/gCnPrD2wU8lJt3C5blmeU/9+0U6/ppRmu6kf/jmm7CMBn
5+
owI0+kdvTF7sbpiUBXTDujXNsqtX0FaksILc9ZAqpUCC2gqRcOXahzT2vnvJ2N+2
6+
bhveG+eB0/5oZcKgx0D4QgjR9k1+thWOQZUCJMg32OYSk4e57WhOQxu9Kh5N2MU1
7+
Ff3fhCYXzg7/GhJtWyDmjt1vNBwGW9Zn0BicySdcVFPCmRW3/rZrSpnwvsEnpIuy
8+
KGq+NMSXAgMBAAECggEAKYtxTFAxWZW4kF1ZEqFzH3juAT0WYyE8x1WcY8mhhDvy
9+
fv5AqH8/qgBe2gGQlp2TL5k2881C184PohaQOnj5rykB3MGj2wgNrgsBlPberBlV
10+
rFZ/iAyh2u93EpMIx+5mNPScjumTCp+P/BBERcrjmrPhp9ii3RUcMVUWzaoj3Lhc
11+
wa5trC1r7UqbUZeO7NaVA7cGETZLVm8U7NaL8ccb1dKASUzrC9QCy9VVekJbb2S7
12+
h38MELR9wvTGS7s4hXQGejb8vEDuXcZzWIFg3YMkJPIyGLAEaRynfeAHm/ji48U0
13+
zh1ba3CWE/6z6nayDPqWqrwic4Hff6Mz+SIWAz2LyQKBgQDcdeWweNRVXhVkcFUP
14+
JNpUiLOF5j3f4nqZwk7j5hQBxcXilYO/lmrcimvhvJ3ox97GfqCkvEQM8thTnPmi
15+
JBagynOfIaUK2qdVwS1BbZ2JpYe3k/rO+iSKtRO4mF94cHgFIafPb5qt0fFz9bDS
16+
7D2lnWSbveMvb+mZsp/+FZx2DwKBgQC3eBhAbOSrSGuh7KOuWsav8pROMdcsESpz
17+
j8el1iEklRsklYiNrVsztlZtNUXE2zSHeNPsGENDGlvKG8qD/vbcdTFsYa1H8Hk5
18+
NydTLAb0/Bm256Xee1Dm5Wt2yG2aLfc9eG0trJz8VgBDhDlulnjo2kavhWIpTBNm
19+
0WmkMQsQ+QKBgQDYXd1PlUbPgcb9DEJu2nxs+r02bQHM+TnaLhm/EdAQ7UmJV7Q2
20+
FCpMyI2YvsU78O1zYlPHWf5vtucZKLbXqxOKOye+xgZ04KPaRf1keXBj51GLmnBN
21+
MrMqbw0r3l/UlI02fBF2RNJKRgHzDO6+E51tLUvQjkyqAewCLI1ZkVw9gQKBgD0F
22+
J2O+E+vX4VxwnRvvOyfn0WWUdBFHAEyBJJDGgC1vniBzz3/3iV7QpTwbPMI1eeoY
23+
yLs8cpqN2LuGtLtkAGzgWXjHn99OXrMl4eFqwkGW22KW9vbhIs44vZ47GSDvasy6
24+
Ee3f/DJ81AegoY1jZIFln57fCP/dOpK20aD3YsvZAoGBAKgaWVYbROCRJ6C8CQGd
25+
yetoZ8n25E7O5JtyKSNGwiQyD0IURgLuotiBpQvCCz9HGS53E6HLzBCc4jZc3GDq
26+
qVDS5cIgcfWAOBalBQ+JxoHsnLRGXeBBKwvaJB+EzlrV8st1dCmM4gukElBJm/PZ
27+
TvEPeiHG81OgB1RPgUt3DVIf
28+
-----END PRIVATE KEY-----

tests/all_tests.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
mod helpers;
2+
mod integration_tests;
3+
14
use helpers::TestEnv;
25
use integration_tests::{
36
api_proxy_test::proxy_api_calls_test,
@@ -7,20 +10,21 @@ use integration_tests::{
710
http_gateway_test::{basic_http_gateway_test, large_assets_http_gateway_test},
811
};
912

10-
mod helpers;
11-
mod integration_tests;
12-
1313
const IC_GATEWAY_DOMAIN: &str = "ic0.app";
14-
const IC_GATEWAY_ADDR: &str = "127.0.0.1:8080";
14+
const IC_GATEWAY_ADDR: &str = "127.0.0.1:18080";
15+
const IC_BOUNDARY_PORT: &str = "18081";
1516

1617
#[tokio::test]
17-
async fn all_intergration_tests() {
18-
let env = TestEnv::new(IC_GATEWAY_ADDR, IC_GATEWAY_DOMAIN).await;
18+
async fn all_intergration_tests() -> anyhow::Result<()> {
19+
let env = TestEnv::new(IC_GATEWAY_ADDR, IC_GATEWAY_DOMAIN, IC_BOUNDARY_PORT).await;
20+
1921
// run all integration tests sequentially
20-
basic_http_gateway_test(&env).await.unwrap();
21-
content_type_headers_test(&env).await.unwrap();
22-
cors_headers_test(&env).await.unwrap();
23-
proxy_api_calls_test(&env).await.unwrap();
24-
large_assets_http_gateway_test(&env).await.unwrap();
25-
denylist_test(&env).await.unwrap();
22+
basic_http_gateway_test(&env).await?;
23+
content_type_headers_test(&env).await?;
24+
cors_headers_test(&env).await?;
25+
proxy_api_calls_test(&env).await?;
26+
large_assets_http_gateway_test(&env).await?;
27+
denylist_test(&env).await?;
28+
29+
Ok(())
2630
}

tests/helpers.rs

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
use std::{
2+
collections::HashMap,
3+
env,
4+
fs::{self, File},
5+
io::Read,
6+
net::SocketAddr,
7+
path::PathBuf,
8+
process::{Child, Command, ExitStatus},
9+
str::FromStr,
10+
time::{Duration, Instant},
11+
};
12+
113
use anyhow::anyhow;
214
use candid::{Encode, Principal};
315
use hex::encode;
@@ -13,21 +25,11 @@ use nix::{
1325
};
1426
use pocket_ic::{
1527
PocketIcBuilder,
28+
common::rest::HttpsConfig,
1629
nonblocking::{PocketIc, update_candid_as},
1730
};
1831
use reqwest::Response;
1932
use sha2::{Digest, Sha256};
20-
use std::{
21-
collections::HashMap,
22-
env, fs,
23-
fs::File,
24-
io::Read,
25-
net::SocketAddr,
26-
path::PathBuf,
27-
process::{Child, Command},
28-
str::FromStr,
29-
time::{Duration, Instant},
30-
};
3133
use tokio::time::sleep;
3234
use tracing::info;
3335

@@ -258,6 +260,7 @@ pub async fn upload_asset_to_asset_canister(
258260
}),
259261
],
260262
};
263+
261264
update_candid_as::<_, ((),)>(
262265
pic,
263266
canister_id,
@@ -269,6 +272,41 @@ pub async fn upload_asset_to_asset_canister(
269272
.unwrap();
270273
}
271274

275+
fn stop_process(p: &mut Child) -> ExitStatus {
276+
let pid = p.id() as i32;
277+
match signal::kill(Pid::from_raw(pid), Signal::SIGINT) {
278+
Ok(_) => info!("Sent SIGINT to process {pid}"),
279+
Err(e) => info!("Failed to send SIGINT: {}", e),
280+
}
281+
p.wait().expect("failed to wait on child process")
282+
}
283+
284+
pub fn start_ic_boundary(port: &str, replica_addr: &str) -> Child {
285+
info!("ic-boundary service starting ...");
286+
let mut cmd = Command::new("./ic-boundary");
287+
cmd.args([
288+
"--listen-http-port",
289+
port,
290+
"--registry-stub-replica",
291+
replica_addr,
292+
"--http-client-timeout-connect",
293+
"3s",
294+
"--skip-replica-tls-verification",
295+
]);
296+
297+
let child = cmd.spawn().expect("failed to start ic-boundary service");
298+
info!("ic-boundary service started");
299+
child
300+
}
301+
302+
pub fn stop_ic_boundary(process: &mut Child) {
303+
info!("gracefully terminating ic-boundary process");
304+
info!(
305+
"ic-boundary process exited with: {:?}",
306+
stop_process(process)
307+
);
308+
}
309+
272310
pub fn start_ic_gateway(
273311
addr: &str,
274312
domain: &str,
@@ -291,36 +329,46 @@ pub fn start_ic_gateway(
291329
cmd.arg(denylist_seed_path.unwrap().to_str().unwrap());
292330
}
293331
cmd.arg("--listen-insecure-serve-http-only");
332+
294333
let child = cmd.spawn().expect("failed to start ic-gateway service");
295334
info!("ic-gateway service started");
296335
child
297336
}
298337

299338
pub fn stop_ic_gateway(process: &mut Child) {
300339
info!("gracefully terminating ic-gateway process");
301-
let pid = process.id() as i32;
302-
match signal::kill(Pid::from_raw(pid), Signal::SIGINT) {
303-
Ok(_) => info!("Sent SIGINT to process {pid}"),
304-
Err(e) => info!("Failed to send SIGINT: {}", e),
305-
}
306-
let exit_status = process.wait().expect("failed to wait on child process");
307-
info!("ic-gateway process exited with: {:?}", exit_status);
340+
info!(
341+
"ic-gateway process exited with: {:?}",
342+
stop_process(process)
343+
);
308344
}
309345

310346
pub struct TestEnv {
311347
pub pic: PocketIc,
312348
pub ic_gateway_process: Child,
349+
pub ic_boundary_process: Child,
313350
pub ic_gateway_addr: SocketAddr,
314351
pub ic_gateway_domain: String,
352+
pub root_key: Vec<u8>,
315353
}
316354

317355
impl TestEnv {
318-
pub async fn new(ic_gateway_addr: &str, ic_gateway_domain: &str) -> Self {
356+
pub async fn new(
357+
ic_gateway_addr: &str,
358+
ic_gateway_domain: &str,
359+
ic_boundary_port: &str,
360+
) -> Self {
319361
init_logging();
320362

321363
info!("pocket-ic server starting ...");
322-
let pic = PocketIcBuilder::new().with_nns_subnet().build_async().await;
323-
pic.auto_progress().await;
364+
let mut pic = PocketIcBuilder::new().with_nns_subnet().build_async().await;
365+
let https_config = HttpsConfig {
366+
cert_path: "test_data/cert.pem".into(),
367+
key_path: "test_data/key.pem".into(),
368+
};
369+
let ic_url = pic
370+
.make_live_with_params(None, None, None, Some(https_config))
371+
.await;
324372
info!("pocket-ic server started");
325373

326374
// fetch the root key of "local" IC and save it to a file
@@ -341,29 +389,36 @@ impl TestEnv {
341389
fs::write(DENYLIST_FILE, &denylist_seed.as_bytes())
342390
.expect("failed to write denylist to file");
343391

392+
let ic_boundary_process = start_ic_boundary(
393+
ic_boundary_port,
394+
&format!("127.0.0.1:{}", ic_url.port_or_known_default().unwrap()),
395+
);
396+
344397
let ic_gateway_addr =
345398
SocketAddr::from_str(ic_gateway_addr).expect("failed to parse address");
346-
let ic_url = format!("{}instances/{}/", pic.get_server_url(), pic.instance_id);
347-
let process = start_ic_gateway(
399+
let ic_gateway_process = start_ic_gateway(
348400
&ic_gateway_addr.to_string(),
349401
ic_gateway_domain,
350-
&ic_url,
402+
&format!("http://127.0.0.1:{ic_boundary_port}"),
351403
ROOT_KEY_FILE.into(),
352404
Some(DENYLIST_FILE.into()),
353405
);
354406

355407
Self {
356-
ic_gateway_process: process,
408+
ic_gateway_process,
409+
ic_boundary_process,
357410
ic_gateway_addr,
358411
ic_gateway_domain: ic_gateway_domain.to_string(),
359412
pic,
413+
root_key,
360414
}
361415
}
362416
}
363417

364418
impl Drop for TestEnv {
365419
fn drop(&mut self) {
366420
stop_ic_gateway(&mut self.ic_gateway_process);
421+
stop_ic_boundary(&mut self.ic_boundary_process);
367422
let _ = std::fs::remove_file("root_key.der");
368423
}
369424
}

tests/integration_tests/api_proxy_test.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ pub async fn proxy_api_calls_test(env: &TestEnv) -> anyhow::Result<()> {
4040
.build()?;
4141

4242
info!("test proxying of various API calls ...");
43-
info!("api/v2/status - implicit status to fetch the root key");
44-
agent.fetch_root_key().await?;
43+
info!("api/v2/status - status call");
44+
let status = agent.status().await?;
45+
assert_eq!(status.replica_health_status, Some("healthy".into()));
46+
47+
// Set the actual root key
48+
agent.set_root_key(env.root_key.clone());
4549

4650
info!("api/v2/query - query counter");
4751
let out = agent.query(&canister_id, "read").call().await?;

tests/integration_tests/content_type_headers_test.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use crate::helpers::TestEnv;
2-
use crate::helpers::{
3-
ExpectedResponse, RETRY_INTERVAL, RETRY_TIMEOUT, check_response, retry_async,
4-
};
1+
use std::collections::HashMap;
2+
53
use anyhow::{Context, anyhow};
64
use http::{Method, StatusCode};
75
use reqwest::{Client, Request};
8-
use std::collections::HashMap;
96
use url::Url;
107

8+
use crate::helpers::{
9+
ExpectedResponse, RETRY_INTERVAL, RETRY_TIMEOUT, TestEnv, check_response, retry_async,
10+
};
11+
1112
// Test scenario:
1213
// - make an HTTP GET request to http://ic0.app/api/v2/status
1314
// - verify necessary headers are present in response: [content-type, x-content-type-options, x-frame-options]

tests/integration_tests/cors_headers_test.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
use std::collections::HashMap;
22

3-
use crate::helpers::{
4-
ExpectedResponse, RETRY_INTERVAL, RETRY_TIMEOUT, TestEnv, check_response, retry_async,
5-
};
63
use anyhow::{Context, anyhow};
74
use candid::Principal;
85
use http::{Method, StatusCode};
@@ -11,6 +8,10 @@ use reqwest::{Client, Request};
118
use tracing::info;
129
use url::Url;
1310

11+
use crate::helpers::{
12+
ExpectedResponse, RETRY_INTERVAL, RETRY_TIMEOUT, TestEnv, check_response, retry_async,
13+
};
14+
1415
// Test scenario:
1516
// - install counter canister
1617
// - make various HTTP requests OPTIONS/GET/POST and verify the CORS headers of the responses

0 commit comments

Comments
 (0)