From 54969a0b5ddb39ffcd523737931411db8227b135 Mon Sep 17 00:00:00 2001
From: yuunlimm <dla0267@gmail.com>
Date: Fri, 17 Jan 2025 11:42:42 -0800
Subject: [PATCH] testing processor

---
 apps/nextra/next.config.mjs                   |   7 +
 .../indexer-sdk/advanced-tutorials/_meta.tsx  |   3 +
 .../advanced-tutorials/processor-test.mdx     | 284 ++++++++++++++++++
 3 files changed, 294 insertions(+)
 create mode 100644 apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/processor-test.mdx

diff --git a/apps/nextra/next.config.mjs b/apps/nextra/next.config.mjs
index 52d9e9e20..3005f620c 100644
--- a/apps/nextra/next.config.mjs
+++ b/apps/nextra/next.config.mjs
@@ -473,6 +473,13 @@ export default withBundleAnalyzer(
           "/en/build/indexer/indexer-sdk/documentation/advanced-tutorials/txn-script",
         permanent: true,
       },
+      {
+        source:
+          "/indexer/indexer-sdk/documentation/advanced-tutorials/processor-test",
+        destination:
+          "/en/build/indexer/indexer-sdk/documentation/advanced-tutorials/processor-test",
+        permanent: true,
+      },
       {
         source: "/indexer/txn-stream/labs-hosted",
         destination: "/en/build/indexer/api/labs-hosted",
diff --git a/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/_meta.tsx b/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/_meta.tsx
index 9b2d4f4db..3da39b585 100644
--- a/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/_meta.tsx
+++ b/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/_meta.tsx
@@ -8,4 +8,7 @@ export default {
   "txn-script": {
     title: "Generating Transactions with Move Scripts",
   },
+  "processor-test": {
+    title: "Testing Your Processor",
+  },
 };
diff --git a/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/processor-test.mdx b/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/processor-test.mdx
new file mode 100644
index 000000000..34b19ef67
--- /dev/null
+++ b/apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/processor-test.mdx
@@ -0,0 +1,284 @@
+---
+title: "Testing Processor"
+---
+
+import { Callout } from "nextra/components"
+
+
+# Overview
+### What Is a Processor?
+A processor is a core component of the Aptos Indexer that handles blockchain transaction processing. It validates, transforms, and stores transactions into a database, enabling downstream applications like analytics, indexing, and querying. Testing the processor ensures that all transactions are correctly handled, maintaining data accuracy and consistency.
+
+
+### What Are We Testing With This?
+
+- **Transaction correctness**: Ensure that each transaction is processed and stored accurately.
+- **Schema consistency**: Verify that the database schema is correctly set up and maintained throughout the tests.
+
+
+### General Flow of how Processor Testing Works
+
+1. You specify the transactions to test
+2. Testing framework SDK spins up a mock gRPC Service with the transactions you specified to return when the processor requests transactions.
+3. Processor processes the transactions and writes the output to a database.
+4. Optionally, you can generate expected database output for validation.
+
+Type of Scenarios it Supports:
+1. A single transaction
+2. A single batch of multiple transactions
+    Input [A, B, C]
+    1. Processor processes A, B, and C
+3. Sequential multiple transaction batches:
+    Input [A, B, C]
+    1. Processor processes A and B
+    2. Processor processes C
+
+## Prerequisites
+1. Ensure Docker Desktop is running for PostgreSQL container support.
+    - **Docker Desktop Installation**: Install Docker Desktop following [this guide](https://docs.docker.com/desktop/) on your machine.
+    - Start Docker Desktop if it's not running
+2. Identify the transactions to test.
+   - Use imported transactions or write your own custom Move scripts to generate test transactions. Refer to [Importing Transaction Guide](./txn-importer.mdx) and [Generating Transaction using Move Script Guide](./txn-script.mdx) for detailed instructions.
+3. Import aptos-indexer-testing-framework to your Cargo.toml
+
+<Callout> - This tutorial assumes you are using Postgres as the database. </Callout>
+- **Adapting to Other Databases**:
+  - Replace PostgreSQL-specific code with relevant database code you intend to use (e.g., MySQL).
+  - Update schema initialization and query methods.
+- **References to Processor Tests**:
+  - Example: [Event Processor Tests](https://github.com/aptos-labs/aptos-indexer-processors/blob/main/rust/integration-tests/src/sdk_tests/events_processor_tests.rs#L139).
+
+
+## Steps to Write a Test
+
+### 1. Set Up the Test Environment
+Before setting up the test environment, it’s important to understand the configurations being used in this step:
+
+
+**What Are These Configurations?**
+
+`generate_file_flag`
+- If `generate_file_flag` is true, the test will overwrite any saved database outputs from previous test runs. If `generate_file_flag` is false, the test will only compare the the actual database output with the expected database output and log differences.
+
+
+`custom_output_path`
+- An optional configuration to specify a custom path where the expected database output will be stored.
+If not provided, the test will use the default path defined by DEFAULT_OUTPUT_FOLDER.
+
+`DEFAULT_OUTPUT_FOLDER`
+- This constant defines the default folder where the system stores output files for the tests.
+Example: "sdk_expected_db_output_files".
+Modify this value in your configuration if you prefer a different default directory.
+
+
+```rust
+let (generate_file_flag, custom_output_path) = get_test_config();
+let output_path = custom_output_path.unwrap_or_else(|| format!("{}/imported_mainnet_txns", DEFAULT_OUTPUT_FOLDER));
+
+// Setup DB and replace as needed
+let mut db = PostgresTestDatabase::new();
+db.setup().await.unwrap();
+
+let mut test_context = SdkTestContext::new(&[CONST_VARIABLE_OF_YOUR_TEST_TRANSACTION]); // Replace with your test transaction
+if test_context.init_mock_grpc().await.is_err() {
+    panic!("Failed to initialize mock grpc");
+};
+```
+
+**Explanation of Each Component:**
+
+`get_test_config():`
+
+This function fetches the configurations (diff_flag and custom_output_path) for the test.
+Modify or extend this function if you want to support additional custom flags or configurations.
+output_path:
+
+Combines DEFAULT_OUTPUT_FOLDER with the subfolder imported_mainnet_txns if no custom_output_path is specified.
+This ensures all output files are stored in a predictable location.
+
+`PostgresTestDatabase::new():`
+
+Creates a new PostgreSQL database instance for testing.
+This database is isolated, ensuring no interference with production or other test environments.
+
+`SdkTestContext::new():`
+
+Initializes the test context with the transaction(s) you want to test.
+Replace CONST_VARIABLE_OF_YOUR_TEST_TRANSACTION with the appropriate variable or constant representing the transaction(s) to be tested.
+
+`init_mock_grpc():`
+
+Initializes a mock gRPC service for the test.
+This allows the processor to simulate transactions without interacting with live blockchain data.
+
+
+### 2. Configure the Processor
+<Callout>
+ - Each test runs in an isolated environment using a PostgreSQL container to prevent interference.
+</Callout>
+
+```rust
+let db_url = db.get_db_url();
+let transaction_stream_config = test_context.create_transaction_stream_config();
+let postgres_config = PostgresConfig {
+    connection_string: db_url.to_string(),
+    db_pool_size: 100,
+};
+
+let db_config = DbConfig::PostgresConfig(postgres_config);
+let default_processor_config = DefaultProcessorConfig {
+    per_table_chunk_sizes: AHashMap::new(),
+    channel_size: 100,
+    deprecated_tables: HashSet::new(),
+};
+
+let processor_config = ProcessorConfig::DefaultProcessor(default_processor_config);
+let processor_name = processor_config.name();
+```
+
+### 3. Create the Processor
+
+```rust
+let processor = DefaultProcessor::new(indexer_processor_config)
+    .await
+    .expect("Failed to create processor");
+```
+Note: Replace `DefaultProcessor` with the processor you are testing.
+
+### 4. Setup a Query
+
+Set up a query to load data from the local database and compare it with expected results, see [example loading function](https://github.com/aptos-labs/aptos-indexer-processors/blob/a8f9c5915f4e3f1f596ed3412b8eb01feca1aa7b/rust/integration-tests/src/diff_test_helper/default_processor.rs#L45)
+
+
+### 5. Setup a Test Context run function
+Use the test_context.run() function to execute the processor, validate outputs using your query, and optionally generate database output files:
+
+<Callout>
+    Key Considerations:
+ - Each test runs in an isolated environment using a PostgreSQL container to prevent interference.
+ - Proper handling of versions ensures transactions are processed and validated in the correct order.
+ - Validation logic must detect changes or issues by comparing processor output with the expected baseline.
+</Callout>
+
+```rust
+    let txn_versions: Vec<i64> = test_context
+        .get_test_transaction_versions()
+        .into_iter()
+        .map(|v| v as i64)
+        .collect();
+
+    let db_values = test_context
+        .run(
+            &processor,
+            generate_file_flag,
+            output_path.clone(),
+            custom_file_name,
+            move || {
+                let mut conn = PgConnection::establish(&db_url).unwrap_or_else(|e| {
+                    eprintln!("[ERROR] Failed to establish DB connection: {:?}", e);
+                    panic!("Failed to establish DB connection: {:?}", e);
+                });
+
+                let db_values = match load_data(&mut conn, txn_versions.clone()) {
+                    Ok(db_data) => db_data,
+                    Err(e) => {
+                        eprintln!("[ERROR] Failed to load data {}", e);
+                        return Err(e);
+                    },
+                };
+
+                if db_values.is_empty() {
+                    eprintln!("[WARNING] No data found for versions: {:?}", txn_versions);
+                }
+
+                Ok(db_values)
+            },
+        )
+```
+
+
+### 6. Run the Processor Test
+
+Once you have your test ready, run the following command to generate the expected output for validation:
+
+```bash
+cargo test sdk_tests -- generate-output
+```
+
+Arguments:
+generate-output: Set this true if you want to generate or overwrite saved database output, or false if you want to compare database outputs in diff mode.
+output-path: it's an optional argument to specify the output path for the db output.
+
+The expected database output will be saved in the specified output_path or `sdk_expected_db_output_files` by default.
+
+
+---
+
+## FAQ
+
+### What Types of Tests Does It Support?
+
+- The testing framework allows you to write tests that compare the database outputs of processors. It helps you catch changes in database output when you're updating or developing your processor.
+
+### What Is `TestContext`?
+
+`TestContext` is a struct that manages:
+
+- `transaction_batches`: A collection of transaction batches.
+- `postgres_container`: A PostgreSQL container for test isolation.
+
+It initializes and manages the database and transaction context for tests.
+
+#### What Does `TestContext.run` Do?
+
+This function executes the processor, applies validation logic, and optionally generates output files.
+
+#### Key Features:
+
+- Flexible Validation: Accepts a user-provided verification function.
+- Multi-Table Support: Handles data across multiple tables.
+- Retries: Uses exponential backoff and timeout for retries.
+- Optional File Generation: Controlled by a flag.
+
+#### Example Usage:
+
+```rust
+pub async fn run<F>(
+    &mut self,
+    processor: &impl ProcessorTrait,
+    txn_version: u64,
+    generate_files: bool,             // Flag to control file generation
+    output_path: String,              // Output path
+    custom_file_name: Option<String>, // Custom file name
+    verification_f: F,                // Verification function
+) -> anyhow::Result<HashMap<String, Value>>
+where
+```
+
+### How to Generate Expected DB Output?
+
+Run the following command:
+
+```bash
+cargo test sdk_tests -- --nocapture generate-output
+```
+
+Supported Test Args:
+
+1. `generate-output`
+2. `output_path`
+
+---
+
+## Troubleshooting and Tips
+
+1. **Isolate Tests**: Use Docker containers for database isolation.
+2. **Handle Non-Deterministic Fields**: Use helpers like `remove_inserted_at` to clean up timestamps before validation.
+3. **Enable Debugging**: Use `eprintln!` for detailed error logging.
+
+#### How to Debug Test Failures?
+run following command to get detailed logs:
+
+```bash
+cargo test sdk_tests -- --nocapture
+```