diff --git a/Cargo.lock b/Cargo.lock index c55c5f979..65202e5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2939,6 +2939,7 @@ dependencies = [ "diesel_migrations", "dotenv", "env_logger", + "lazy_static", "log", "md5", "meilisearch-sdk", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 232557651..2370af57d 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -71,3 +71,7 @@ solana-program = { version = "1.9.4", optional = true } cid = { version = "0.7.0", optional = true } url = "2.2.2" md5 = { version = "0.7.0", optional = true } + + +[dev-dependencies] +lazy_static = "1.4.0" \ No newline at end of file diff --git a/crates/core/src/db/mod.rs b/crates/core/src/db/mod.rs index b750bb57e..2a221e1b0 100644 --- a/crates/core/src/db/mod.rs +++ b/crates/core/src/db/mod.rs @@ -209,3 +209,378 @@ pub fn connect(args: ConnectArgs, mode: ConnectMode) -> Result { Ok(ConnectResult { pool, ty, migrated }) } + +#[cfg(test)] +pub mod test { + use diesel::{insert_into, prelude::*}; + use uuid::Uuid; + + use super::{ + connect, models, + schema::{ + auction_houses, listings, metadata_collection_keys, metadata_jsons, metadatas, + purchases, + }, + ConnectArgs, + }; + use crate::prelude::*; + + fn initialize() -> super::Pool { + let conn_args = ConnectArgs { + database_read_url: None, + database_write_url: Some( + "postgres://postgres:holap1ex@localhost:5337/holaplex-indexer".into(), + ), + database_url: None, + }; + let (pool, _) = connect(conn_args, crate::db::ConnectMode::Write) + .expect("failed to connect to database"); + let conn = pool.get().expect("failed to get connection to database"); + + let nft_a_metadata_address = Borrowed("metadata_a"); + let nft_b_metadata_address = Borrowed("metadata_b"); + let nft_c_metadata_address = Borrowed("metadata_c"); + let nft_d_metadata_address = Borrowed("metadata_d"); + let nft_d_purchase_id = Some( + Uuid::parse_str("00000000-0000-0000-0000-000000000009").expect("failed to parse UUID"), + ); + let collection_metadata_address = Borrowed("collection_a"); + let auction_house_address = Borrowed("auction_house_a"); + let seller_address = Borrowed("seller_a"); + let buyer_address = Borrowed("buyer_a"); + + insert_into(metadata_collection_keys::table) + .values(vec![ + models::MetadataCollectionKey { + metadata_address: nft_a_metadata_address.clone(), + collection_address: collection_metadata_address.clone(), + verified: true, + }, + models::MetadataCollectionKey { + metadata_address: nft_b_metadata_address.clone(), + collection_address: collection_metadata_address.clone(), + verified: true, + }, + models::MetadataCollectionKey { + metadata_address: nft_c_metadata_address.clone(), + collection_address: collection_metadata_address.clone(), + verified: true, + }, + models::MetadataCollectionKey { + metadata_address: nft_d_metadata_address.clone(), + collection_address: collection_metadata_address.clone(), + verified: true, + }, + ]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed metadata_collection_keys"); + + insert_into(metadatas::table) + .values(vec![ + models::Metadata { + address: nft_a_metadata_address.clone(), + name: Borrowed("nft A"), + symbol: Borrowed("symbol"), + uri: Borrowed("http://example.com/nft-a-uri"), + seller_fee_basis_points: 100, + update_authority_address: Borrowed("update authority"), + mint_address: collection_metadata_address.clone(), + primary_sale_happened: true, + is_mutable: false, + edition_nonce: None, + edition_pda: Borrowed("nft edition pda"), + token_standard: None, + slot: Some(0), + burned: false, + }, + models::Metadata { + address: nft_b_metadata_address.clone(), + name: Borrowed("nft B"), + symbol: Borrowed("symbol"), + uri: Borrowed("http://example.com/nft-b-uri"), + seller_fee_basis_points: 100, + update_authority_address: Borrowed("update authority"), + mint_address: collection_metadata_address.clone(), + primary_sale_happened: true, + is_mutable: false, + edition_nonce: None, + edition_pda: Borrowed("nft edition pda"), + token_standard: None, + slot: Some(0), + burned: false, + }, + models::Metadata { + address: nft_c_metadata_address.clone(), + name: Borrowed("nft C"), + symbol: Borrowed("symbol"), + uri: Borrowed("http://example.com/nft-c-uri"), + seller_fee_basis_points: 100, + update_authority_address: Borrowed("update authority"), + mint_address: collection_metadata_address.clone(), + primary_sale_happened: true, + is_mutable: false, + edition_nonce: None, + edition_pda: Borrowed("nft edition pda"), + token_standard: None, + slot: Some(0), + burned: false, + }, + models::Metadata { + address: nft_d_metadata_address.clone(), + name: Borrowed("nft D"), + symbol: Borrowed("symbol"), + uri: Borrowed("http://example.com/nft-d-uri"), + seller_fee_basis_points: 100, + update_authority_address: Borrowed("update authority"), + mint_address: collection_metadata_address.clone(), + primary_sale_happened: true, + is_mutable: false, + edition_nonce: None, + edition_pda: Borrowed("nft edition pda"), + token_standard: None, + slot: Some(0), + burned: false, + }, + models::Metadata { + address: collection_metadata_address.clone(), + name: Borrowed("collection name"), + symbol: Borrowed("symbol"), + uri: Borrowed("http://example.com/collection-uri"), + seller_fee_basis_points: 100, + update_authority_address: Borrowed("update authority"), + mint_address: Borrowed("collection mint"), + primary_sale_happened: true, + is_mutable: false, + edition_nonce: None, + edition_pda: Borrowed("collection edition pda"), + token_standard: None, + slot: Some(0), + burned: false, + }, + ]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed metadatas"); + + insert_into(auction_houses::table) + .values(vec![models::AuctionHouse { + address: auction_house_address.clone(), + treasury_mint: Borrowed("So11111111111111111111111111111111111111112"), + auction_house_treasury: Borrowed("treasury"), + treasury_withdrawal_destination: Borrowed("treasury withdrawal"), + fee_withdrawal_destination: Borrowed("fee withdrawal"), + authority: Borrowed("auction house authority"), + creator: Borrowed("auction house creator"), + bump: 0, + treasury_bump: 0, + fee_payer_bump: 0, + seller_fee_basis_points: 100, + requires_sign_off: false, + can_change_sale_price: false, + auction_house_fee_account: Borrowed("auction house fee account"), + }]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed auction_houses"); + + insert_into(listings::table) + .values(vec![ + models::Listing { + id: Some( + Uuid::parse_str("00000000-0000-0000-0000-000000000001") + .expect("failed to parse UUID"), + ), + trade_state: Borrowed("nft_a trade state"), + auction_house: auction_house_address.clone(), + seller: seller_address.clone(), + metadata: nft_a_metadata_address.clone(), + purchase_id: None, + price: 1, + token_size: 1, + trade_state_bump: 0, + created_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + canceled_at: None, + slot: 0, + write_version: Some(0), + }, + models::Listing { + id: Some( + Uuid::parse_str("00000000-0000-0000-0000-000000000002") + .expect("failed to parse UUID"), + ), + trade_state: Borrowed("nft_b trade state"), + auction_house: auction_house_address.clone(), + seller: seller_address.clone(), + metadata: nft_b_metadata_address.clone(), + purchase_id: None, + price: 1, + token_size: 1, + trade_state_bump: 0, + created_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + canceled_at: None, + slot: 0, + write_version: Some(0), + }, + models::Listing { + id: Some( + Uuid::parse_str("00000000-0000-0000-0000-000000000003") + .expect("failed to parse UUID"), + ), + trade_state: Borrowed("nft_c trade state"), + auction_house: auction_house_address.clone(), + seller: seller_address.clone(), + metadata: nft_c_metadata_address.clone(), + purchase_id: None, + price: 1, + token_size: 1, + trade_state_bump: 0, + created_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + canceled_at: None, + slot: 0, + write_version: Some(0), + }, + models::Listing { + id: Some( + Uuid::parse_str("00000000-0000-0000-0000-000000000004") + .expect("failed to parse UUID"), + ), + trade_state: Borrowed("nft_d trade state"), + auction_house: auction_house_address.clone(), + seller: seller_address.clone(), + metadata: nft_d_metadata_address.clone(), + purchase_id: nft_d_purchase_id.clone(), + price: 1, + token_size: 1, + trade_state_bump: 0, + created_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + canceled_at: None, + slot: 0, + write_version: Some(0), + }, + ]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed purchases"); + + insert_into(purchases::table) + .values(vec![models::Purchase { + id: nft_d_purchase_id.clone(), + buyer: buyer_address.clone(), + seller: seller_address.clone(), + auction_house: auction_house_address.clone(), + metadata: nft_d_metadata_address.clone(), + token_size: 1, + price: 1, + created_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + slot: 0, + write_version: None, + }]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed purchases"); + + insert_into(metadata_jsons::table) + .values(vec![ + models::MetadataJson { + metadata_address: nft_a_metadata_address, + fingerprint: Borrowed(&Vec::::new()), + updated_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + description: Some(Borrowed("nft A description")), + image: Some(Borrowed("http://example.com/nft-a-image")), + animation_url: Some(Borrowed("http://example.com/nft-a-animation")), + external_url: Some(Borrowed("http://example.com/nft-a-external")), + category: Some(Borrowed("nft A category")), + raw_content: Borrowed( + &serde_json::from_str("{}") + .expect("Failed to deserialize metadata content"), + ), + model: Some(Borrowed("model")), + fetch_uri: Borrowed("http://example.com/nft-a-fetch-uri"), + slot: 0, + write_version: 0, + }, + models::MetadataJson { + metadata_address: nft_b_metadata_address, + fingerprint: Borrowed(&Vec::::new()), + updated_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + description: Some(Borrowed("nft B description")), + image: Some(Borrowed("http://example.com/nft-b-image")), + animation_url: Some(Borrowed("http://example.com/nft-b-animation")), + external_url: Some(Borrowed("http://example.com/nft-b-external")), + category: Some(Borrowed("nft B category")), + raw_content: Borrowed( + &serde_json::from_str("{}") + .expect("Failed to deserialize metadata content"), + ), + model: Some(Borrowed("model")), + fetch_uri: Borrowed("http://example.com/nft-b-fetch-uri"), + slot: 0, + write_version: 0, + }, + models::MetadataJson { + metadata_address: nft_c_metadata_address, + fingerprint: Borrowed(&Vec::::new()), + updated_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + description: Some(Borrowed("nft C description")), + image: Some(Borrowed("http://example.com/nft-c-image")), + animation_url: Some(Borrowed("http://example.com/nft-c-animation")), + external_url: Some(Borrowed("http://example.com/nft-c-external")), + category: Some(Borrowed("nft C category")), + raw_content: Borrowed( + &serde_json::from_str("{}") + .expect("Failed to deserialize metadata content"), + ), + model: Some(Borrowed("model")), + fetch_uri: Borrowed("http://example.com/nft-c-fetch-uri"), + slot: 0, + write_version: 0, + }, + models::MetadataJson { + metadata_address: nft_d_metadata_address, + fingerprint: Borrowed(&Vec::::new()), + updated_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + description: Some(Borrowed("nft C description")), + image: Some(Borrowed("http://example.com/nft-c-image")), + animation_url: Some(Borrowed("http://example.com/nft-c-animation")), + external_url: Some(Borrowed("http://example.com/nft-c-external")), + category: Some(Borrowed("nft B category")), + raw_content: Borrowed( + &serde_json::from_str("{}") + .expect("Failed to deserialize metadata content"), + ), + model: Some(Borrowed("model")), + fetch_uri: Borrowed("http://example.com/nft-c-fetch-uri"), + slot: 0, + write_version: 0, + }, + models::MetadataJson { + metadata_address: collection_metadata_address, + fingerprint: Borrowed(&Vec::::new()), + updated_at: NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0), + description: Some(Borrowed("collection description")), + image: Some(Borrowed("http://example.com/collection-image")), + animation_url: Some(Borrowed("http://example.com/collection-animation")), + external_url: Some(Borrowed("http://example.com/collection-external")), + category: Some(Borrowed("collection category")), + raw_content: Borrowed( + &serde_json::from_str("{}") + .expect("Failed to deserialize metadata content"), + ), + model: Some(Borrowed("model")), + fetch_uri: Borrowed("http://example.com/collection-fetch-uri"), + slot: 0, + write_version: 0, + }, + ]) + .on_conflict_do_nothing() + .execute(&conn) + .expect("failed to seed metadata_jsons"); + + pool + } + + lazy_static::lazy_static! { + pub static ref DATABASE: super::Pool = initialize(); + } +} diff --git a/crates/core/src/db/queries/collections.rs b/crates/core/src/db/queries/collections.rs index 73680818b..7483163f5 100644 --- a/crates/core/src/db/queries/collections.rs +++ b/crates/core/src/db/queries/collections.rs @@ -235,3 +235,51 @@ pub fn collection_activities( .load(conn) .context("Failed to load collection activities") } + +#[cfg(test)] +mod tests { + use chrono::{TimeZone, Utc}; + + use crate::db::test::DATABASE; + + #[test] + fn test_collections_featured_by_marketcap_returns_non_empty() { + let pool = &DATABASE; + + let result = super::by_market_cap( + &pool.get().expect("failed to aquire database connection"), + None::>, + crate::db::custom_types::OrderDirection::Desc, + Utc.ymd(1901, 1, 1).and_hms(0, 0, 0), + Utc.ymd(3000, 1, 1).and_hms(0, 0, 0), + 50, + 0, + ) + .expect("failed query"); + + assert!(!result.is_empty(), "expected at least one row"); + } + + #[test] + fn test_collections_featured_by_volume_returns_non_empty() { + let pool = &DATABASE; + + let result = super::by_volume( + &pool.get().expect("failed to aquire database connection"), + None::>, + crate::db::custom_types::OrderDirection::Desc, + Utc.ymd(1901, 1, 1).and_hms(0, 0, 0), + Utc.ymd(3000, 1, 1).and_hms(0, 0, 0), + 50, + 0, + ) + .expect("failed query"); + + assert!(!result.is_empty(), "expected at least one row"); + } + + #[test] + fn test_collection_activities() { + todo!("Test collection_activities()"); + } +} diff --git a/crates/core/src/db/queries/nft_count.rs b/crates/core/src/db/queries/nft_count.rs index e471adad6..3c9833b99 100644 --- a/crates/core/src/db/queries/nft_count.rs +++ b/crates/core/src/db/queries/nft_count.rs @@ -303,3 +303,19 @@ pub fn store_creators( .load(conn) .context("Failed to load store creators counts") } + +#[cfg(test)] +mod tests { + use crate::db::test::DATABASE; + + #[test] + fn test_store_creators_minimal_passes() { + let pool = &DATABASE; + + let _ = super::store_creators( + &pool.get().expect("failed to aquire database connection"), + Vec::::new(), + ) + .expect("failed query"); + } +}