use std::env; use chrono::{Duration, TimeZone, Utc}; use num_traits::Zero; use serde_json::Value as JsonValue; use shopless_database::addresses::{self, Country, NewAddress}; use shopless_database::invoices::{self, NewInvoice}; use shopless_database::line_item_options::{self, NewLineItemOption}; use shopless_database::line_items::{self, NewLineItem}; use shopless_database::orders::{self, NewOrder, OrderState}; use shopless_database::Cents; use sqlx::{Connection, PgConnection}; use uuid::Uuid; async fn setup() -> PgConnection { let database_url = env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://shopless@localhost/shopless".to_string()); let test_database_url = env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://shopless@localhost/shopless_test".to_string()); shopless_migration::reset_test_db(&database_url) .await .unwrap(); PgConnection::connect(&test_database_url).await.unwrap() } #[tokio::test] async fn database_tests() { let mut conn = setup().await; // Address let new_address = NewAddress { recipient: "Name", line1: "Street 1", line2: "Floor 2", postal_code: "12345", city: "The City", province_code: Some("TP"), province_name: Some("The Province"), country_code: Country::DE, country_name: "Germany", phone: Some("0123456789"), }; let shipping_address = addresses::insert(&new_address, &mut conn).await.unwrap(); { // deconstruct to not miss new properties in the future let NewAddress { recipient, line1, line2, postal_code, city, province_code, province_name, country_code, country_name, phone, } = new_address; assert_eq!(shipping_address.recipient, recipient); assert_eq!(shipping_address.line1, line1); assert_eq!(shipping_address.line2, line2); assert_eq!(shipping_address.postal_code, postal_code); assert_eq!(shipping_address.city, city); assert_eq!(shipping_address.province_code.as_deref(), province_code); assert_eq!(shipping_address.province_name.as_deref(), province_name); assert_eq!(shipping_address.country_code, country_code); assert_eq!(shipping_address.country_name, country_name); assert_eq!(shipping_address.phone.as_deref(), phone); } assert_eq!( addresses::fetch(shipping_address.id, &mut conn) .await .unwrap() .as_ref(), Some(&shipping_address) ); let invoice_address = addresses::insert(&new_address, &mut conn).await.unwrap(); // Order let new_order = NewOrder { uuid: Uuid::new_v4(), origin: "http://localhost", locale: "de", state: OrderState::Created, custom_status: "new", date: Utc.ymd(2020, 7, 11).and_hms(17, 3, 42), payment_method: "cash", payment_id: "1234", payment_meta: &JsonValue::from(true), currency: "EUR", shipping_method: "mail", shipping_method_name: "Mail", coupon: None, shipping_taxrate: 19, shipping: Cents::from(500), tax: Cents::from(239), discount: Cents::zero(), total: Cents::from(1500), email: "foo@bar.foobar", token: "1hg234i12hk3whj1k2j", prioritize: true, note: Some("foobar"), invoice_address_id: invoice_address.id, shipping_address_id: shipping_address.id, }; let order = orders::insert(&new_order, &mut conn).await.unwrap(); { // deconstruct to not miss new properties in the future let NewOrder { uuid, origin, locale, state, custom_status, date, payment_method, payment_id, payment_meta, currency, shipping_method, shipping_method_name, coupon, shipping_taxrate, shipping, tax, discount, total, email, token, prioritize, note, invoice_address_id, shipping_address_id, } = new_order; assert_eq!(order.uuid, uuid); assert_eq!(order.origin, origin); assert_eq!(order.locale, locale); assert_eq!(order.state, state); assert_eq!(order.custom_status, custom_status); assert_eq!(order.date, date); assert_eq!(order.payment_method, payment_method); assert_eq!(order.payment_id, payment_id); assert_eq!(&order.payment_meta, payment_meta); assert_eq!(order.currency, currency); assert_eq!(order.shipping_method, shipping_method); assert_eq!(order.shipping_method_name, shipping_method_name); assert_eq!(order.coupon.as_deref(), coupon); assert_eq!(order.shipping_taxrate, shipping_taxrate); assert_eq!(order.shipping, shipping); assert_eq!(order.tax, tax); assert_eq!(order.discount, discount); assert_eq!(order.total, total); assert_eq!(order.email, email); assert_eq!(order.token, token); assert_eq!(order.prioritize, prioritize); assert_eq!(order.note.as_deref(), note); assert_eq!(order.invoice_address_id, invoice_address_id); assert_eq!(order.shipping_address_id, shipping_address_id); } assert_eq!( orders::fetch(order.id, &mut conn).await.unwrap().as_ref(), Some(&order) ); // Line Item let new_line_item = NewLineItem { order_id: order.id, sku: "test", url: "http://localhost/test", name: "Test", quantity: 2, price: Cents::from(1000), taxrate: 19, total: Cents::from(2000), }; let line_item = line_items::insert(&new_line_item, &mut conn).await.unwrap(); { // deconstruct to not miss new properties in the future let NewLineItem { order_id, sku, url, name, quantity, price, taxrate, total, } = new_line_item; assert_eq!(line_item.order_id, order_id); assert_eq!(line_item.sku, sku); assert_eq!(line_item.url, url); assert_eq!(line_item.name, name); assert_eq!(line_item.quantity, quantity); assert_eq!(line_item.price, price); assert_eq!(line_item.taxrate, taxrate); assert_eq!(line_item.total, total); } assert_eq!( line_items::fetch(line_item.id, &mut conn) .await .unwrap() .as_ref(), Some(&line_item) ); // Line Item Option let new_line_item_option = NewLineItemOption { line_item_id: line_item.id, sku: "length", name: "Length", value: JsonValue::from(42), price: Cents::from(200), }; let line_item_option = line_item_options::insert(&new_line_item_option, &mut conn) .await .unwrap(); { // deconstruct to not miss new properties in the future let NewLineItemOption { line_item_id, sku, name, value, price, } = new_line_item_option; assert_eq!(line_item_option.line_item_id, line_item_id); assert_eq!(line_item_option.sku, sku); assert_eq!(line_item_option.name, name); assert_eq!(line_item_option.value, value); assert_eq!(line_item_option.price, price); } assert_eq!( line_item_options::fetch(line_item_option.id, &mut conn) .await .unwrap() .as_ref(), Some(&line_item_option) ); // Invoice let shipment = JsonValue::from("tracking-number: 123124124124"); let new_invoice = NewInvoice { order_id: order.id, invoice_date: Utc.ymd(2020, 7, 11).naive_utc(), payment_method_name: "Cash", payment_transaction_id: "41263567781892312", payment_instructions: Some("no further steps necessary"), shipment: Some(&shipment), }; let invoice = invoices::insert(&new_invoice, &mut conn).await.unwrap(); { // deconstruct to not miss new properties in the future let NewInvoice { order_id, invoice_date, payment_method_name, payment_transaction_id, payment_instructions, shipment, } = new_invoice; assert_eq!(invoice.order_id, order_id); assert_eq!(invoice.invoice_date, invoice_date); assert_eq!(invoice.payment_method_name, payment_method_name); assert_eq!(invoice.payment_transaction_id, payment_transaction_id); assert_eq!( invoice.payment_instructions.as_deref(), payment_instructions ); assert_eq!(invoice.shipment.as_ref(), shipment); } assert_eq!( invoices::fetch(invoice.invoice_id, &mut conn) .await .unwrap() .as_ref(), Some(&invoice) ); // test payment_method/_id conflict match orders::insert(&new_order, &mut conn).await { Err(sqlx::Error::RowNotFound) => {} _ => panic!("expected sqlx::Error::RowNotFound"), } // test updated_at when updating order sqlx::query("select pg_sleep(0.1);") .execute(&mut conn) .await .unwrap(); sqlx::query("UPDATE orders SET custom_status = 'payment-received' WHERE id = $1") .bind(order.id) .execute(&mut conn) .await .unwrap(); let order = orders::fetch(order.id, &mut conn).await.unwrap().unwrap(); let delta = Utc::now() - order.updated_at; if delta > Duration::milliseconds(10) { panic!( "expected updated_at to get updated when updating order (delta: {}ms)", delta.num_milliseconds() ); } // test updated_at when updating line item sqlx::query("select pg_sleep(0.1);") .execute(&mut conn) .await .unwrap(); sqlx::query("UPDATE line_items SET sku = 'foobar' WHERE id = $1") .bind(line_item.id) .execute(&mut conn) .await .unwrap(); let order = orders::fetch(order.id, &mut conn).await.unwrap().unwrap(); let delta = Utc::now() - order.updated_at; if delta > Duration::milliseconds(10) { panic!( "expected updated_at to get updated when updating line item (delta: {}ms)", delta.num_milliseconds() ); } // test updated_at when updating line item option sqlx::query("select pg_sleep(0.1);") .execute(&mut conn) .await .unwrap(); sqlx::query("UPDATE line_item_options SET sku = 'foobar' WHERE id = $1") .bind(line_item_option.id) .execute(&mut conn) .await .unwrap(); let order = orders::fetch(order.id, &mut conn).await.unwrap().unwrap(); let delta = Utc::now() - order.updated_at; if delta > Duration::milliseconds(10) { panic!( "expected updated_at to get updated when updating line item option (delta: {}ms)", delta.num_milliseconds() ); } // test updated_at when updating address sqlx::query("select pg_sleep(0.1);") .execute(&mut conn) .await .unwrap(); sqlx::query("UPDATE addresses SET line2 = 'foobar' WHERE id = $1") .bind(shipping_address.id) .execute(&mut conn) .await .unwrap(); let order = orders::fetch(order.id, &mut conn).await.unwrap().unwrap(); let delta = Utc::now() - order.updated_at; if delta > Duration::milliseconds(10) { panic!( "expected updated_at to get updated when updating line item option (delta: {}ms)", delta.num_milliseconds() ); } }