This PR reworks how we process Stripe events for reconciliation purposes. The previous approach in #15480 turns out to not be workable, on account of the Stripe event IDs not being strictly in order. This meant that we couldn't reliably compare two arbitrary event IDs and determine which one was more recent. This new approach leans on the guidance that Stripe provides for webhooks events: > Webhook endpoints might occasionally receive the same event more than once. You can guard against duplicated event receipts by logging the [event IDs](https://docs.stripe.com/api/events/object#event_object-id) you’ve processed, and then not processing already-logged events. > > https://docs.stripe.com/webhooks#handle-duplicate-events We now record processed Stripe events in the `processed_stripe_events` table and use this to filter out events that have already been processed, so we do not process them again. When retrieving events from the Stripe events API we now buffer the unprocessed events so that we can sort them by their `created` timestamp and process them in (roughly) the order they occurred. Release Notes: - N/A
193 lines
5.7 KiB
Rust
193 lines
5.7 KiB
Rust
mod billing_subscription_tests;
|
|
mod buffer_tests;
|
|
mod channel_tests;
|
|
mod contributor_tests;
|
|
mod db_tests;
|
|
// we only run postgres tests on macos right now
|
|
#[cfg(target_os = "macos")]
|
|
mod embedding_tests;
|
|
mod extension_tests;
|
|
mod feature_flag_tests;
|
|
mod message_tests;
|
|
mod processed_stripe_event_tests;
|
|
|
|
use super::*;
|
|
use gpui::BackgroundExecutor;
|
|
use parking_lot::Mutex;
|
|
use sea_orm::ConnectionTrait;
|
|
use sqlx::migrate::MigrateDatabase;
|
|
use std::sync::{
|
|
atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
|
|
Arc,
|
|
};
|
|
|
|
pub struct TestDb {
|
|
pub db: Option<Arc<Database>>,
|
|
pub connection: Option<sqlx::AnyConnection>,
|
|
}
|
|
|
|
impl TestDb {
|
|
pub fn sqlite(background: BackgroundExecutor) -> Self {
|
|
let url = "sqlite::memory:";
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_io()
|
|
.enable_time()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let mut db = runtime.block_on(async {
|
|
let mut options = ConnectOptions::new(url);
|
|
options.max_connections(5);
|
|
let mut db = Database::new(options, Executor::Deterministic(background))
|
|
.await
|
|
.unwrap();
|
|
let sql = include_str!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/migrations.sqlite/20221109000000_test_schema.sql"
|
|
));
|
|
db.pool
|
|
.execute(sea_orm::Statement::from_string(
|
|
db.pool.get_database_backend(),
|
|
sql,
|
|
))
|
|
.await
|
|
.unwrap();
|
|
db.initialize_notification_kinds().await.unwrap();
|
|
db
|
|
});
|
|
|
|
db.runtime = Some(runtime);
|
|
|
|
Self {
|
|
db: Some(Arc::new(db)),
|
|
connection: None,
|
|
}
|
|
}
|
|
|
|
pub fn postgres(background: BackgroundExecutor) -> Self {
|
|
static LOCK: Mutex<()> = Mutex::new(());
|
|
|
|
let _guard = LOCK.lock();
|
|
let mut rng = StdRng::from_entropy();
|
|
let url = format!(
|
|
"postgres://postgres@localhost/zed-test-{}",
|
|
rng.gen::<u128>()
|
|
);
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_io()
|
|
.enable_time()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let mut db = runtime.block_on(async {
|
|
sqlx::Postgres::create_database(&url)
|
|
.await
|
|
.expect("failed to create test db");
|
|
let mut options = ConnectOptions::new(url);
|
|
options
|
|
.max_connections(5)
|
|
.idle_timeout(Duration::from_secs(0));
|
|
let mut db = Database::new(options, Executor::Deterministic(background))
|
|
.await
|
|
.unwrap();
|
|
let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
|
|
db.migrate(Path::new(migrations_path), false).await.unwrap();
|
|
db.initialize_notification_kinds().await.unwrap();
|
|
db
|
|
});
|
|
|
|
db.runtime = Some(runtime);
|
|
|
|
Self {
|
|
db: Some(Arc::new(db)),
|
|
connection: None,
|
|
}
|
|
}
|
|
|
|
pub fn db(&self) -> &Arc<Database> {
|
|
self.db.as_ref().unwrap()
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! test_both_dbs {
|
|
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
|
|
#[cfg(target_os = "macos")]
|
|
#[gpui::test]
|
|
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
|
|
let test_db = $crate::db::TestDb::postgres(cx.executor().clone());
|
|
$test_name(test_db.db()).await;
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
|
|
let test_db = $crate::db::TestDb::sqlite(cx.executor().clone());
|
|
$test_name(test_db.db()).await;
|
|
}
|
|
};
|
|
}
|
|
|
|
impl Drop for TestDb {
|
|
fn drop(&mut self) {
|
|
let db = self.db.take().unwrap();
|
|
if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
|
|
db.runtime.as_ref().unwrap().block_on(async {
|
|
use util::ResultExt;
|
|
let query = "
|
|
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
FROM pg_stat_activity
|
|
WHERE
|
|
pg_stat_activity.datname = current_database() AND
|
|
pid <> pg_backend_pid();
|
|
";
|
|
db.pool
|
|
.execute(sea_orm::Statement::from_string(
|
|
db.pool.get_database_backend(),
|
|
query,
|
|
))
|
|
.await
|
|
.log_err();
|
|
sqlx::Postgres::drop_database(db.options.get_url())
|
|
.await
|
|
.log_err();
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str)]) -> Vec<Channel> {
|
|
channels
|
|
.iter()
|
|
.map(|(id, parent_path, name)| Channel {
|
|
id: *id,
|
|
name: name.to_string(),
|
|
visibility: ChannelVisibility::Members,
|
|
parent_path: parent_path.to_vec(),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
|
|
|
|
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
|
|
db.create_user(
|
|
email,
|
|
false,
|
|
NewUserParams {
|
|
github_login: email[0..email.find('@').unwrap()].to_string(),
|
|
github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.user_id
|
|
}
|
|
|
|
static TEST_CONNECTION_ID: AtomicU32 = AtomicU32::new(1);
|
|
fn new_test_connection(server: ServerId) -> ConnectionId {
|
|
ConnectionId {
|
|
id: TEST_CONNECTION_ID.fetch_add(1, SeqCst),
|
|
owner_id: server.0 as u32,
|
|
}
|
|
}
|