Skip to content

Commit

Permalink
tests: e2e tests scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Apr 21, 2023
1 parent 6ecde1d commit eedb0ce
Show file tree
Hide file tree
Showing 20 changed files with 304 additions and 28 deletions.
7 changes: 7 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[alias]
cov = "llvm-cov"
cov-lcov = "llvm-cov --lcov --output-path=./.coverage/lcov.info"
cov-html = "llvm-cov --html"
time = "build --timings --all-targets"
e2e = "test --features e2e-tests"

1 change: 1 addition & 0 deletions .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ TORRUST_IDX_BACK_CONFIG=
TORRUST_IDX_BACK_USER_UID=1000
TORRUST_TRACKER_CONFIG=
TORRUST_TRACKER_USER_UID=1000
TORRUST_TRACKER_API_TOKEN=MyAccessToken
35 changes: 12 additions & 23 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Development Checks

on: [push,pull_request]
on: [push, pull_request]

jobs:
run:
Expand All @@ -14,27 +14,16 @@ jobs:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Format
uses: ClementTsang/cargo-action@main
with:
command: fmt
args: --all --check
run: cargo fmt --all --check
- name: Check
uses: ClementTsang/cargo-action@main
with:
command: check
args: --all-targets
run: cargo check --all-targets
- name: Clippy
uses: ClementTsang/cargo-action@main
with:
command: clippy
args: --all-targets
- name: Build
uses: ClementTsang/cargo-action@main
with:
command: build
args: --all-targets
- name: Test
uses: ClementTsang/cargo-action@main
with:
command: test
args: --all-targets
run: cargo clippy --all-targets
- name: Unit and integration tests
run: cargo test --all-targets
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest
- name: Test Coverage
run: cargo llvm-cov nextest
- name: E2E Tests
run: ./docker/bin/run-e2e-tests.sh
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- uses: Swatinem/rust-cache@v1
- name: Run tests
run: cargo test
- name: Stop databases
working-directory: ./tests
run: docker-compose down

tag:
needs: test
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/.coverage/
/.env
/config.toml
/data_v2.db*
/data.db*
/storage/
/target
/uploads/

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ default-run = "main"
[profile.dev.package.sqlx-macros]
opt-level = 3

[features]
e2e-tests = []

[dependencies]
actix-web = "4.0.0-beta.8"
actix-multipart = "0.4.0-beta.5"
Expand Down
3 changes: 3 additions & 0 deletions bin/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ if ! [ -f "./config.toml" ]; then
cp ./config.toml.local ./config.toml
fi

# Generate storage directory if it does not exist
mkdir -p "./storage/database"

# Generate the sqlite database for the index baclend if it does not exist
if ! [ -f "./storage/database/data.db" ]; then
# todo: it should get the path from config.toml and only do it when we use sqlite
Expand Down
27 changes: 27 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,30 @@ services:
idx-back:
build:
context: .
args:
RUN_AS_USER: appuser
UID: ${TORRUST_IDX_BACK_USER_UID:-1000}
target: development
user: ${TORRUST_IDX_BACK_USER_UID:-1000}:${TORRUST_IDX_BACK_USER_UID:-1000}
tty: true
environment:
- TORRUST_IDX_BACK_CONFIG=${TORRUST_IDX_BACK_CONFIG}
- CARGO_HOME=/home/appuser/.cargo
networks:
- server_side
ports:
- 3000:3000
# todo: implement healthcheck
#healthcheck:
# test:
# [
# "CMD-SHELL",
# "cargo run healthcheck"
# ]
# interval: 10s
# retries: 5
# start_period: 10s
# timeout: 3s
volumes:
- ./:/app
- ~/.cargo:/home/appuser/.cargo
Expand All @@ -25,11 +40,23 @@ services:
tty: true
environment:
- TORRUST_TRACKER_CONFIG=${TORRUST_TRACKER_CONFIG}
- TORRUST_TRACKER_API_TOKEN=${TORRUST_TRACKER_API_TOKEN:-MyAccessToken}
networks:
- server_side
ports:
- 6969:6969/udp
- 1212:1212/tcp
# todo: implement healthcheck
#healthcheck:
# test:
# [
# "CMD-SHELL",
# "/app/main healthcheck"
# ]
# interval: 10s
# retries: 5
# start_period: 10s
# timeout: 3s
volumes:
- ./storage:/app/storage
depends_on:
Expand Down
4 changes: 2 additions & 2 deletions config-idx-back.toml.local
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ max_password_length = 64
secret_key = "MaxVerstappenWC2021"

[database]
#connect_url = "sqlite://storage/database/data.db?mode=rwc" # SQLite
connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_backend" # MySQL
connect_url = "sqlite://storage/database/data.db?mode=rwc" # SQLite
#connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_backend" # MySQL
torrent_info_update_interval = 3600

[mail]
Expand Down
8 changes: 5 additions & 3 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ If you want to inject an environment variable into docker-compose you can use th
Build and run it locally:

```s
TORRUST_TRACKER_CONFIG=$(cat config-tracker.toml.local) \
TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.toml.local) \
docker compose up --build
TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \
TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.toml.local) \
TORRUST_TRACKER_CONFIG=$(cat config-tracker.toml.local) \
TORRUST_TRACKER_API_TOKEN=${TORRUST_TRACKER_API_TOKEN:-MyAccessToken} \
docker compose up -d --build
```

After running the "up" command you will have three running containers:
Expand Down
3 changes: 3 additions & 0 deletions docker/bin/e2e-env-down.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

docker compose down
10 changes: 10 additions & 0 deletions docker/bin/e2e-env-up.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \
docker compose build

TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \
TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.toml.local) \
TORRUST_TRACKER_CONFIG=$(cat config-tracker.toml.local) \
TORRUST_TRACKER_API_TOKEN=${TORRUST_TRACKER_API_TOKEN:-MyAccessToken} \
docker compose up -d
57 changes: 57 additions & 0 deletions docker/bin/run-e2e-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash

CURRENT_USER_NAME=$(whoami)
CURRENT_USER_ID=$(id -u)
echo "User name: $CURRENT_USER_NAME"
echo "User id: $CURRENT_USER_ID"

TORRUST_IDX_BACK_USER_UID=$CURRENT_USER_ID
TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID
export TORRUST_IDX_BACK_USER_UID
export TORRUST_TRACKER_USER_UID

wait_for_container_to_be_healthy() {
local container_name="$1"
local max_retries="$2"
local retry_interval="$3"
local retry_count=0

while [ $retry_count -lt "$max_retries" ]; do
container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")"
if [ "$container_health" != "{}" ]; then
container_status="$(echo "$container_health" | jq -r '.Status')"
if [ "$container_status" == "healthy" ]; then
echo "Container $container_name is healthy"
return 0
fi
fi

retry_count=$((retry_count + 1))
echo "Waiting for container $container_name to become healthy (attempt $retry_count of $max_retries)..."
sleep "$retry_interval"
done

echo "Timeout reached, container $container_name is not healthy"
return 1
}

cp .env.local .env
./bin/install.sh

# Start E2E testing environment
./docker/bin/e2e-env-up.sh

wait_for_container_to_be_healthy torrust-mysql-1 10 3
# todo: implement healthchecks for tracker and backend and wait until they are healthy
#wait_for_container torrust-tracker-1 10 3
#wait_for_container torrust-idx-back-1 10 3
sleep 20s

# Just to make sure that everything is up and running
docker ps

# Run E2E tests
cargo test --features e2e-tests

# Stop E2E testing environment
./docker/bin/e2e-env-down.sh
67 changes: 67 additions & 0 deletions tests/e2e/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use reqwest::Response;

use crate::e2e::connection_info::ConnectionInfo;
use crate::e2e::http::{Query, ReqwestQuery};

/// API Client
pub struct Client {
connection_info: ConnectionInfo,
base_path: String,
}

impl Client {
pub fn new(connection_info: ConnectionInfo) -> Self {
Self {
connection_info,
base_path: "/".to_string(),
}
}

pub async fn entrypoint(&self) -> Response {
self.get("", Query::default()).await
}

pub async fn get(&self, path: &str, params: Query) -> Response {
self.get_request_with_query(path, params).await
}

/*
pub async fn post(&self, path: &str) -> Response {
reqwest::Client::new().post(self.base_url(path).clone()).send().await.unwrap()
}
async fn delete(&self, path: &str) -> Response {
reqwest::Client::new()
.delete(self.base_url(path).clone())
.send()
.await
.unwrap()
}
pub async fn get_request(&self, path: &str) -> Response {
get(&self.base_url(path), None).await
}
*/

pub async fn get_request_with_query(&self, path: &str, params: Query) -> Response {
get(&self.base_url(path), Some(params)).await
}

fn base_url(&self, path: &str) -> String {
format!("http://{}{}{path}", &self.connection_info.bind_address, &self.base_path)
}
}

async fn get(path: &str, query: Option<Query>) -> Response {
match query {
Some(params) => reqwest::Client::builder()
.build()
.unwrap()
.get(path)
.query(&ReqwestQuery::from(params))
.send()
.await
.unwrap(),
None => reqwest::Client::builder().build().unwrap().get(path).send().await.unwrap(),
}
}
16 changes: 16 additions & 0 deletions tests/e2e/connection_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pub fn connection_with_no_token(bind_address: &str) -> ConnectionInfo {
ConnectionInfo::anonymous(bind_address)
}

#[derive(Clone)]
pub struct ConnectionInfo {
pub bind_address: String,
}

impl ConnectionInfo {
pub fn anonymous(bind_address: &str) -> Self {
Self {
bind_address: bind_address.to_string(),
}
}
}
21 changes: 21 additions & 0 deletions tests/e2e/contexts/about.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::e2e::client::Client;
use crate::e2e::connection_info::connection_with_no_token;

#[tokio::test]
#[cfg_attr(not(feature = "e2e-tests"), ignore)]
async fn it_should_load_the_about_page_at_the_api_entrypoint() {
let client = Client::new(connection_with_no_token("localhost:3000"));

let response = client.entrypoint().await;

assert_eq!(response.status(), 200);
assert_eq!(response.headers().get("content-type").unwrap(), "text/html; charset=utf-8");

let title = format!("<title>About</title>");
let response_text = response.text().await.unwrap();

assert!(
response_text.contains(&title),
":\n response: `\"{response_text}\"`\n does not contain: `\"{title}\"`."
);
}
1 change: 1 addition & 0 deletions tests/e2e/contexts/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod about;
Loading

0 comments on commit eedb0ce

Please sign in to comment.