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

Enhance Test Coverage for Receipt Insertion, Batch Processing, and Sender Accounts #572

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

carlosvdr
Copy link
Collaborator

@carlosvdr carlosvdr commented Jan 6, 2025

This new tests cover are here to enhance the testing experience and help ensure tap V2 wont affect the processes in a "bigger picture"
As well as added more test types around receiving receipts. For this a new method was added regarding batch inserts for receipts into the DB, trying to be have a more accurate representation of how TAP works on a day to day basis.

Regarding the tests on the "bigger picture" , tests for several layers were added.
Such as a full process from sender account all the way from starting it, creating a sender allocation up to the point of marking allocation as last for redeeming.

Another test added from the sender account manager layer which tries to do the same process as the previous layer but starting from the manager.

And finally a test for the full Tap Agent and simulate as closely as possible the full process of it

@carlosvdr carlosvdr self-assigned this Jan 6, 2025
@coveralls
Copy link

coveralls commented Jan 6, 2025

Pull Request Test Coverage Report for Build 12835420725

Details

  • 239 of 250 (95.6%) changed or added relevant lines in 5 files are covered.
  • 5 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.4%) to 78.022%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/tap-agent/src/agent/sender_accounts_manager.rs 47 49 95.92%
crates/tap-agent/src/test.rs 54 56 96.43%
crates/tap-agent/src/agent/sender_account.rs 38 41 92.68%
crates/tap-agent/src/tap_agent_test.rs 93 97 95.88%
Files with Coverage Reduction New Missed Lines %
crates/tap-agent/src/agent/sender_account.rs 1 84.41%
crates/watcher/src/lib.rs 4 92.71%
Totals Coverage Status
Change from base Build 12810605983: 0.4%
Covered Lines: 7015
Relevant Lines: 8991

💛 - Coveralls

@carlosvdr carlosvdr requested a review from gusinacio January 6, 2025 16:51
@carlosvdr carlosvdr marked this pull request as ready for review January 13, 2025 19:39
@carlosvdr carlosvdr changed the title Extra tap tests Enhance Test Coverage for Receipt Insertion, Batch Processing, and Sender Accounts” Jan 14, 2025
@carlosvdr carlosvdr changed the title Enhance Test Coverage for Receipt Insertion, Batch Processing, and Sender Accounts” Enhance Test Coverage for Receipt Insertion, Batch Processing, and Sender Accounts Jan 14, 2025
Copy link
Contributor

@suchapalaver suchapalaver left a comment

Choose a reason for hiding this comment

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

@carlosvdr can you squash/fix the commit history, please 🙏

suchapalaver
suchapalaver previously approved these changes Jan 14, 2025
@suchapalaver
Copy link
Contributor

@carlosvdr , approving just to remove the block 👍

Comment on lines 1152 to 1185
async fn mock_escrow_subgraph_empty_response() -> (MockServer, MockGuard) {
let mock_ecrow_subgraph_server: MockServer = MockServer::start().await;
let _mock_ecrow_subgraph = mock_ecrow_subgraph_server
.register_as_scoped(
Mock::given(method("POST"))
.and(body_string_contains("TapTransactions"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "data": {
"transactions": [],
}
}))),
)
.await;
(mock_ecrow_subgraph_server, _mock_ecrow_subgraph)
}

Copy link
Contributor

@suchapalaver suchapalaver Jan 14, 2025

Choose a reason for hiding this comment

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

Did you consider implementing this in a "lazy" way since it could be called by multiple tests in parallel and is deterministic? (https://stackoverflow.com/a/59614532/24786256)

Comment on lines 649 to 677
async fn mock_escrow_subgraph_empty_response() -> (MockServer, MockGuard) {
let mock_ecrow_subgraph_server: MockServer = MockServer::start().await;
let _mock_ecrow_subgraph = mock_ecrow_subgraph_server
.register_as_scoped(
Mock::given(method("POST"))
//.and(body_string_contains("TapTransactions"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "data": {
"transactions": [],
}
}))),
)
.await;
(mock_ecrow_subgraph_server, _mock_ecrow_subgraph)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I had the same thought about "lazy static" here as well: https://stackoverflow.com/a/59614532/24786256

Comment on lines 1135 to 1163
lazy_static! {
static ref MOCK_ESCROW_SUBGRAPH: Mutex<Option<Arc<(MockServer, MockGuard)>>> =
Mutex::new(None);
static ref MOCK_EMPTY_ESCROW_SUBGRAPH: Mutex<Option<Arc<(MockServer, MockGuard)>>> =
Mutex::new(None);
}

async fn mock_escrow_subgraph() -> Arc<(MockServer, MockGuard)> {
let mut guard = MOCK_ESCROW_SUBGRAPH.lock().unwrap();
if let Some(server) = guard.as_ref() {
server.clone()
} else {
let mock_ecrow_subgraph_server = MockServer::start().await;
let mock_ecrow_subgraph = mock_ecrow_subgraph_server
.register_as_scoped(
Mock::given(method("POST"))
.and(body_string_contains("TapTransactions"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "data": {
"transactions": [{
"id": "0x00224ee6ad4ae77b817b4e509dc29d644da9004ad0c44005a7f34481d421256409000000"
}],
}
"transactions": [{
"id": "0x00224ee6ad4ae77b817b4e509dc29d644da9004ad0c44005a7f34481d421256409000000"
}],
}
}))),
)
.await;
(mock_ecrow_subgraph_server, _mock_ecrow_subgraph)
let server = Arc::new((mock_ecrow_subgraph_server, mock_ecrow_subgraph));
*guard = Some(server.clone());
server
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@carlosvdr this is on the right track but I suggest looking at how others use OnceCell instead of the Mutex. I don't have time to try this myself right now, but I would expect things to look more like:

use lazy_static::lazy_static;
use tokio::sync::{OnceCell, Mutex};
use std::sync::Arc;
use wiremock::{Mock, MockServer, MockGuard, ResponseTemplate};
use wiremock::matchers::{method, body_string_contains};
use serde_json::json;

lazy_static! {
    static ref MOCK_ESCROW_SUBGRAPH: OnceCell<Arc<(MockServer, MockGuard)>> = OnceCell::new();
}

async fn mock_escrow_subgraph() -> Arc<(MockServer, MockGuard)> {
    MOCK_ESCROW_SUBGRAPH
        .get_or_try_init(async {
            let mock_server = MockServer::start().await;
            let mock_guard = mock_server
                .register_as_scoped(
                    Mock::given(method("POST"))
                        .and(body_string_contains("TapTransactions"))
                        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                            "data": {
                                "transactions": [{
                                    "id": "0x00224ee6ad4ae77b817b4e509dc29d644da9004ad0c44005a7f34481d421256409000000"
                                }]
                            }
                        }))),
                )
                .await;
            Ok::<_, wiremock::Error>(Arc::new((mock_server, mock_guard)))
        })
        .await
        .unwrap()
        .clone()
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks let me take a look into this

Copy link
Contributor

@suchapalaver suchapalaver left a comment

Choose a reason for hiding this comment

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

I really want to see what @gusinacio thinks, @carlosvdr!

Comment on lines +651 to +676
lazy_static! {
static ref MOCK_EMPTY_ESCROW_SUBGRAPH: OnceCell<Arc<(MockServer, MockGuard)>> =
OnceCell::new();
}
async fn mock_escrow_subgraph_empty_response() -> Arc<(MockServer, MockGuard)> {
MOCK_EMPTY_ESCROW_SUBGRAPH
.get_or_try_init(|| async {
let mock_ecrow_subgraph_server: MockServer = MockServer::start().await;
let _mock_ecrow_subgraph = mock_ecrow_subgraph_server
.register_as_scoped(
Mock::given(method("POST"))
.and(body_string_contains("TapTransactions"))
.respond_with(ResponseTemplate::new(200).set_body_json(
json!({ "data": {
"transactions": [],
}
}),
)),
)
.await;
Ok::<_, anyhow::Error>(Arc::new((mock_ecrow_subgraph_server, _mock_ecrow_subgraph)))
})
.await
.unwrap()
.clone()
}
Copy link
Member

Choose a reason for hiding this comment

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

Hey, I see what you did here, but I think it's not needed to use lazy_static, since it's used by a single test.

Also, since you are using register_as_scoped, it doesn't make sense to have a scoped route that is never dropped.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe rstest is better for this context.

Comment on lines +134 to +141
let actor = TestableActor::new(SenderAccountsManager);
let notify = actor.notify.clone();
(
prefix,
notify,
Actor::spawn(None, actor, args).await.unwrap(),
handle_aggregator,
)
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need a TestableActor here? You don't seem to wait for any notification.

Comment on lines +1 to +13
// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs.
// SPDX-License-Identifier: Apache-2.0

use std::{
collections::HashMap,
str::FromStr,
sync::{atomic::AtomicU32, Arc},
time::Duration,
};

use indexer_monitor::{DeploymentDetails, EscrowAccounts, SubgraphClient};
use jsonrpsee::server::ServerHandle;
use ractor::{call, concurrency::JoinHandle, Actor, ActorRef};
Copy link
Member

Choose a reason for hiding this comment

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

This files seems to me that it could be added to tests/integration_test.rs. It seems self-contained.

It all depends if you can use crate:: as indexer_tap_agent::. Maybe the only part that is harder is to migrate create_received_receipt and store_batch_receipts. Maybe we could add something in test-assets

Copy link
Member

Choose a reason for hiding this comment

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

Oh, it seems that you might also have trouble with SenderAllocationMessage::GetUnaggregatedReceipts

Copy link
Member

Choose a reason for hiding this comment

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

Ok, one suggestion that I have is the following:

  • add a feature flag called "test" in Cargo.toml
[features]
test = []
  • in every #[cfg(cfg)] that we have (that are not tests), replace with #[cfg(any(test, feature = "test"))]
  • add in dev_dependencies in Cargo.toml
[dev_dependencies]
crate_name = { path = ".", features = ["test"] }

I've tried this in the past but I got errors with rust-analyzer. Maybe this is solved, check it out if this works or please let me know the errors.

This is a little bit hacky, but rust doesn't have a straightforward way to do this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess when you said "these files", you meant all those files that contain integration tests?
Also
I did tried separating this into another crate like tests/integration_tests, something similar in a separate refactor but it was too much of a hassle, this created some circular dependency issues

Copy link
Member

Choose a reason for hiding this comment

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

Try to create tests/integration_tests in tap-agent.

Comment on lines +83 to +87
let mut sender_aggregator_endpoints: HashMap<Address, Url> = HashMap::new();
sender_aggregator_endpoints.insert(
TAP_SENDER.1,
Url::from_str(&format!("http://{}", aggregator_endpoint)).expect("This should not fail"),
);
Copy link
Member

Choose a reason for hiding this comment

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

Here comes a little cool trick.

let sender_aggregator_endpoints: HashMap<_, _> = vec![(
			TAP_SENDER.1, 
			Url::from_str(&format!("http://{}", aggregator_endpoint)).unwrap()
		)].into_iter().collect();

Comment on lines +851 to +882
#[sqlx::test(migrations = "../../migrations")]
async fn test_account_manger_to_sender_allocation_closing(pgpool: PgPool) {
// Start a TAP aggregator server.
let (handle, aggregator_endpoint) = run_server(
0,
SIGNER.0.clone(),
vec![SIGNER.1].into_iter().collect(),
TAP_EIP712_DOMAIN_SEPARATOR.clone(),
100 * 1024,
100 * 1024,
1,
)
.await
.unwrap();

let aggregator_endpoint = Some(
Url::from_str(&("http://".to_owned() + &aggregator_endpoint.to_string()))
.expect("This shouldn't fail"),
);

let mock_network_subgraph_server: MockServer = MockServer::start().await;

mock_network_subgraph_server
.register(
Mock::given(method("POST"))
.and(body_string_contains("ClosedAllocations"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "data": {
"meta": {
"block": {
"number": 1,
"hash": "hash",
"timestamp": 1
Copy link
Member

Choose a reason for hiding this comment

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

This test for me seems like an integration tests and not really unit tests. You are creating a SenderAcountManager, that is creating SenderAccounts, that is creating SenderAllocations.

This should be placed somewhere else IMO.

@@ -761,7 +815,8 @@ mod tests {

#[sqlx::test(migrations = "../../migrations")]
async fn test_update_sender_allocation(pgpool: PgPool) {
let (prefix, notify, (actor, join_handle)) = create_sender_accounts_manager(pgpool).await;
let (prefix, notify, (actor, join_handle)) =
create_sender_accounts_manager(pgpool, None, None, None).await;
Copy link
Member

Choose a reason for hiding this comment

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

Nones all over the place seems a little bit of code smell.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same as other, do you then recommend creating a different create_sender_account_manager
I thought so too, but was too much repeated code, went with the None instead to make it more "flexible"

Copy link
Member

Choose a reason for hiding this comment

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

Maybe use bon to update create_sender_accounts_manager then all None become default and you don't need to provide them.

Comment on lines +1134 to +1144
lazy_static! {
static ref MOCK_EMPTY_ESCROW_SUBGRAPH: OnceCell<Arc<(MockServer, MockGuard)>> =
OnceCell::new();
static ref MOCK_ESCROW_SUBGRAPH: OnceCell<Arc<(MockServer, MockGuard)>> = OnceCell::new();
}

async fn mock_escrow_subgraph() -> Arc<(MockServer, MockGuard)> {
MOCK_ESCROW_SUBGRAPH
.get_or_try_init(|| async {
let mock_server = MockServer::start().await;
let mock_guard = mock_server
Copy link
Member

Choose a reason for hiding this comment

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

Are we getting much performance or speed by doing that?

The same applies as before: MockGuard needs to be dropped to be effective, it doesn't make sense to put one of those in static.

In general, I don't really like to use static things in tests, it's really good to isolate each test with its own data.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe this is a case to use rstest again.

Comment on lines +1455 to +1464
let rav_marked_as_last = sqlx::query!(
r#"
SELECT * FROM scalar_tap_ravs WHERE last = true;
"#,
)
.fetch_all(&pgpool)
.await
.expect("Should not fail to fetch from scalar_tap_ravs");
assert!(!rav_marked_as_last.is_empty());
// Stop the TAP aggregator server.
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be testing things from SenderAllocation. It seems to me that this is an integration test.

@@ -1262,6 +1271,184 @@ pub mod tests {
handle.stopped().await;
}

#[sqlx::test(migrations = "../../migrations")]
Copy link
Member

Choose a reason for hiding this comment

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

You could do the following:

async fn execute<Fut>(pgpool: PgPool, populate: impl FnOnce(PgPool) -> Fut) where:
Fut: Future<Output = ()> {
    ..
    populate(pgpool.clone()).await;
    ..
}

#[sqlx::test(migrations = "../../migrations")]
 async fn test_several_receipts_rav_request(pgpool: PgPool) {
    execute(pgpool, |pgpool| async {
         // Add receipts to the database.
        const AMOUNT_OF_RECEIPTS: u64 = 1000;
        for i in 0..AMOUNT_OF_RECEIPTS {
            let receipt = create_received_receipt(&ALLOCATION_ID_0, &SIGNER.0, i, i + 1, i.into());
            store_receipt(&pgpool, receipt.signed_receipt())
                .await
                .unwrap();
        }
    }
}

#[sqlx::test(migrations = "../../migrations")]
 async fn test_several_receipts_batch_insert_rav_request(pgpool: PgPool) {
    execute(pgpool, |pgpool| async {
         // Add batch receipts to the database.
        const AMOUNT_OF_RECEIPTS: u64 = 1000;
        let mut receipts: Vec<ReceiptWithState<Checking>> = Vec::with_capacity(1000);
        for i in 0..AMOUNT_OF_RECEIPTS {
            let receipt = create_received_receipt(&ALLOCATION_ID_0, &SIGNER.0, i, i + 1, i.into());
            receipts.push(receipt);
        }
        let res = store_batch_receipts(&pgpool, receipts).await;
        assert!(res.is_ok());
    }
}

You would be able to reduce this test in half, and also could create multiple ways to populate and execute the same test.

You also would need to add a few Send, Sync and 'static as trait bounds but the compiler should help you with that.

Comment on lines 935 to 940
pgpool: PgPool,
sender_aggregator_endpoint: String,
escrow_subgraph_endpoint: &str,
rav_request_receipt_limit: u64,
sender_account: Option<ActorRef<SenderAccountMessage>>,
) -> SenderAllocationArgs {
Copy link
Member

Choose a reason for hiding this comment

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

This may be a little out of the scope, but one way to make this better is to add bon and annotate this function with #[bon::builder]. You are able to set default and only provide member that you actually need to change.

https://bon-rs.com/

Copy link
Member

Choose a reason for hiding this comment

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

Feel free to use it or not, it's your choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants