Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: refactor service tests as standalone #249

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,11 @@ jobs:
sleep 1
done

- name: Create example graph
run: docker exec neo4j bash /db-graph/run-queries.sh

# - name: Install dependencies
# run: cargo build --release

- name: Run the service
run: |
cargo run > service.log 2>&1 &
sleep 10 # Give the service a moment to start

- name: Run integration tests
run: cargo test

- name: Show service logs if tests fail
if: failure()
run: cat service.log
run: SYNC_DB=true cargo test

- name: Tear down Docker Compose
if: always()
Expand Down
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,28 @@ To get started with Nexus, first set up the required databases: Neo4j and Redis.
1. Clone the repository and navigate to the project directory.
2. Copy the environment template and set up the Docker environment:

```bash
cd docker
cp .env-sample .env
docker-compose up -d
```
```bash
cd docker
cp .env-sample .env
docker-compose up -d
```

3. Populate the Neo4j database with initial data:

```bash
docker exec neo4j bash /db-graph/run-queries.sh
```
```bash
docker exec neo4j bash /db-graph/run-queries.sh
```

Once the `Neo4j` graph database is seeded with data, the next step is to populate the `Redis` database by running the _nexus-service_

> If the Redis cache is empty, the nexus-service will handle it automatically. If not follow the steps of warning section

4. Run the Nexus service:

```bash
cargo run
```
```bash
cargo run
```

5. **Access Redis and Neo4j UIs**:
- Redis UI: [http://localhost:8001/redis-stack/browser](http://localhost:8001/redis-stack/browser)
- Neo4J UI: [http://localhost:7474/browser/](http://localhost:7474/browser/)
Expand All @@ -95,6 +96,13 @@ To run all tests:
cargo test
```

You can optionally pass the `SYNC_DB` env var to control setting up the testing data in mocks folder.
You can pass:

- `true`: To set up the data in graph and reindex.
- `false`: To skip setting up database for test. (when you've already done so)
- `graph`: Only run the graph database mocks.

To test specific modules or features:

```bash
Expand All @@ -114,16 +122,16 @@ If tests or the development environment seem out of sync, follow these steps to

1. **Reset Neo4j**:

```bash
docker exec neo4j bash -c "cypher-shell -u neo4j -p 12345678 'MATCH (n) DETACH DELETE n;'"
docker exec neo4j bash /db-graph/run-queries.sh
```
```bash
docker exec neo4j bash -c "cypher-shell -u neo4j -p 12345678 'MATCH (n) DETACH DELETE n;'"
docker exec neo4j bash /db-graph/run-queries.sh
```

2. **Re-index Redis Cache**:

```bash
REINDEX=true cargo run
```
```bash
REINDEX=true cargo run
```

## 🌐 Useful Links

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod config;
mod db;
pub mod db;
mod error;
pub mod events;
pub mod models;
Expand Down
2 changes: 1 addition & 1 deletion src/models/post/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ mod tests {
const REPLY_ID: &str = "2ZECXVXHZBE00";
const POST_ID: &str = "2ZECRNM66G900";

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_post_details_get_from_graph() {
// Open connections against ddbb
let config = Config::from_env();
Expand Down
2 changes: 1 addition & 1 deletion src/models/user/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ mod tests {
"not_existing_user_id_either", // Does not exist
];

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_get_by_ids_from_redis() {
let config = Config::from_env();
setup(&config).await;
Expand Down
1 change: 1 addition & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod service;
pub mod utils;
pub mod watcher;
118 changes: 54 additions & 64 deletions tests/service/all.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use crate::service::utils::{get_request, invalid_get_request, post_request};
use anyhow::Result;
use pubky_nexus::models::tag::TagDetails;
use reqwest::StatusCode;
use serde_json::json;
use std::{
fs::{self, create_dir_all, remove_file, File},
io::Write,
};

use anyhow::Result;
use pubky_nexus::models::tag::TagDetails;
use serde_json::json;
use crate::utils::TestServiceServer;

const HOST_URL: &str = "http://localhost:8080";

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_static_serving() -> Result<()> {
TestServiceServer::get_test_server().await;
let client = httpc_test::new_client(HOST_URL)?;
let test_file_path = "static";
let test_file_name = "foobar";
Expand Down Expand Up @@ -48,35 +52,30 @@ async fn test_static_serving() -> Result<()> {
Ok(())
}

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_file_details() -> Result<()> {
let client = httpc_test::new_client(HOST_URL)?;
let test_file_id = "2ZK2H8P2T5NG0";
let test_file_user = "y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy";

let test_file_uri = format!("pubky://{test_file_user}/pub/pubky.app/files/{test_file_id}");

let res = client
.do_get(
format!(
"/v0/files/file/{}",
url::form_urlencoded::byte_serialize(test_file_uri.as_bytes()).collect::<String>()
)
.as_str(),
let res = get_request(
format!(
"/v0/files/file/{}",
url::form_urlencoded::byte_serialize(test_file_uri.as_bytes()).collect::<String>()
)
.await?;
.as_str(),
)
.await?;

let json_body = res.json_body()?;
assert_eq!(res.status(), 200);
assert_eq!(json_body["id"], test_file_id);
assert_eq!(json_body["owner_id"], test_file_user);
assert_eq!(res["id"], test_file_id);
assert_eq!(res["owner_id"], test_file_user);

Ok(())
}

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_files_by_ids() -> Result<()> {
let client = httpc_test::new_client(HOST_URL)?;
let test_file_id = "2ZK2H8P2T5NG0";
let test_file_user = "y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy";
let test_file_uri = format!("pubky://{test_file_user}/pub/pubky.app/files/{test_file_id}");
Expand All @@ -85,72 +84,63 @@ async fn test_files_by_ids() -> Result<()> {
let test_file_user2 = "sfgetccnq7s3h57a7imf6n7k5fqxus33yg85f1ndhnrnofjdmhjy";
let test_file_uri2 = format!("pubky://{test_file_user2}/pub/pubky.app/files/{test_file_id2}");

let res = client
.do_post(
"/v0/files/by-ids",
json!({"uris": [test_file_uri, test_file_uri2]}),
)
.await?;

let json_body = res.json_body()?;
let res = post_request(
"/v0/files/by-ids",
json!({"uris": [test_file_uri, test_file_uri2]}),
)
.await?;

assert_eq!(res.status(), 200);
assert_eq!(json_body.as_array().unwrap().len(), 2);
assert_eq!(res.as_array().unwrap().len(), 2);

assert_eq!(json_body[0]["id"], test_file_id);
assert_eq!(json_body[0]["owner_id"], test_file_user);
assert_eq!(json_body[1]["id"], test_file_id2);
assert_eq!(json_body[1]["owner_id"], test_file_user2);
assert_eq!(res[0]["id"], test_file_id);
assert_eq!(res[0]["owner_id"], test_file_user);
assert_eq!(res[1]["id"], test_file_id2);
assert_eq!(res[1]["owner_id"], test_file_user2);

Ok(())
}

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_get_post() -> Result<()> {
let client = httpc_test::new_client(HOST_URL)?;

let author_id = "y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy";
let post_id = "2ZCW1TGR5BKG0";

let res = client
.do_get(&format!(
"/v0/post/{}/{}?viewer_id={}",
author_id, post_id, author_id
))
.await?;
assert_eq!(res.status(), 200);

let body = res.json_body()?;

assert_eq!(body["details"]["content"], "I am told we can reply now!");
assert_eq!(body["details"]["indexed_at"].as_u64(), Some(1718616844478));
assert_eq!(body["details"]["id"], post_id);
assert_eq!(body["details"]["author"], author_id);
assert_eq!(body["details"]["attachments"].as_array().unwrap().len(), 1);
let res = get_request(&format!(
"/v0/post/{}/{}?viewer_id={}",
author_id, post_id, author_id
))
.await?;

assert_eq!(res["details"]["content"], "I am told we can reply now!");
assert_eq!(res["details"]["indexed_at"].as_u64(), Some(1718616844478));
assert_eq!(res["details"]["id"], post_id);
assert_eq!(res["details"]["author"], author_id);
assert_eq!(res["details"]["attachments"].as_array().unwrap().len(), 1);
assert_eq!(
(body["details"]["attachments"].as_array().unwrap())[0],
(res["details"]["attachments"].as_array().unwrap())[0],
"pubky://y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy/pub/pubky.app/files/2ZKH7K7M9G3G0".to_string()
);
assert_eq!(
body["details"]["uri"],
res["details"]["uri"],
"pubky://y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy/pub/pubky.app/posts/2ZCW1TGR5BKG0"
);
assert_eq!(body["counts"]["tags"].as_u64(), Some(5));
assert_eq!(body["counts"]["replies"].as_u64(), Some(2));
assert_eq!(body["counts"]["reposts"].as_u64(), Some(1));
assert_eq!(body["bookmark"]["indexed_at"].as_u64(), Some(1721764200000));
assert_eq!(body["bookmark"]["id"], "2Z9PFGC3WWWW0");
assert_eq!(res["counts"]["tags"].as_u64(), Some(5));
assert_eq!(res["counts"]["replies"].as_u64(), Some(2));
assert_eq!(res["counts"]["reposts"].as_u64(), Some(1));
assert_eq!(res["bookmark"]["indexed_at"].as_u64(), Some(1721764200000));
assert_eq!(res["bookmark"]["id"], "2Z9PFGC3WWWW0");

// Panic if tags vector is bigger that 1
let post_tag_object = body["tags"][0].clone();
let post_tag_object = res["tags"][0].clone();
let post_tag: TagDetails = serde_json::from_value(post_tag_object.clone())?;
assert_eq!(post_tag.label, "pubky");

// Test non-existing post
let res = client
.do_get(&format!("/v0/post/{}/{}", author_id, "no_post"))
.await?;
assert_eq!(res.status(), 404);
invalid_get_request(
&format!("/v0/post/{}/{}", author_id, "no_post"),
StatusCode::NOT_FOUND,
)
.await?;

Ok(())
}
11 changes: 8 additions & 3 deletions tests/service/endpoints.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::Result;

use crate::utils::TestServiceServer;

const HOST_URL: &str = "http://localhost:8080";

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_swagger_ui() -> Result<()> {
TestServiceServer::get_test_server().await;
let client = httpc_test::new_client(HOST_URL)?;

let res = client.do_get("/swagger-ui").await?;
Expand All @@ -14,8 +17,9 @@ async fn test_swagger_ui() -> Result<()> {
Ok(())
}

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_openapi_schema() -> Result<()> {
TestServiceServer::get_test_server().await;
let client = httpc_test::new_client(HOST_URL)?;

let res = client.do_get("/api-docs/openapi.json").await?;
Expand All @@ -30,8 +34,9 @@ async fn test_openapi_schema() -> Result<()> {
Ok(())
}

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_info_endpoint() -> Result<()> {
TestServiceServer::get_test_server().await;
let client = httpc_test::new_client(HOST_URL)?;

let res = client.do_get("/v0/info").await?;
Expand Down
6 changes: 3 additions & 3 deletions tests/service/stream/post/author.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use super::{ROOT_PATH, USER_ID};
use crate::service::utils::make_request;
use crate::service::utils::get_request;
use anyhow::Result;

#[tokio::test]
#[tokio_shared_rt::test(shared)]
async fn test_stream_user_posts() -> Result<()> {
let path = format!(
"{ROOT_PATH}?author_id={}&source=author&sorting=timeline",
USER_ID
);
let body = make_request(&path).await?;
let body = get_request(&path).await?;

assert!(body.is_array());

Expand Down
6 changes: 3 additions & 3 deletions tests/service/stream/post/author_replies.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use super::ROOT_PATH;
use crate::service::utils::make_request;
use crate::service::utils::get_request;
use anyhow::Result;

#[tokio::test]
#[tokio_shared_rt::test(shared)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the benefits for service tests of using the shared tokio runtime?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using it can cause conflict when using the connectors.

async fn test_stream_user_replies() -> Result<()> {
let author_id = "pxnu33x7jtpx9ar1ytsi4yxbp6a5o36gwhffs8zoxmbuptici1jy";
let path = format!(
"{ROOT_PATH}?author_id={}&source=author_replies&sorting=timeline",
author_id
);
let body = make_request(&path).await?;
let body = get_request(&path).await?;

assert!(body.is_array());

Expand Down
Loading