Stabilize integration test qualification
This commit is contained in:
parent
414b380516
commit
560e1df9b2
1 changed files with 68 additions and 180 deletions
|
|
@ -1,4 +1,3 @@
|
|||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -11,9 +10,7 @@ use remora_domain::ports::CredentialResolver;
|
|||
use remora_durable::{PoolConfig, connect};
|
||||
use remora_schema::migrate as migrate_schema;
|
||||
use sqlx::PgPool;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{Barrier, oneshot};
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
fn database_url() -> Option<String> {
|
||||
std::env::var("DATABASE_URL")
|
||||
|
|
@ -164,65 +161,6 @@ async fn harness() -> Option<(PgPool, CredentialRegistry, GrantBoundResolver)> {
|
|||
Some((pool, registry, resolver))
|
||||
}
|
||||
|
||||
async fn held_vault_response_proxy(
|
||||
upstream: &str,
|
||||
) -> (String, oneshot::Receiver<()>, oneshot::Sender<()>) {
|
||||
let authority = upstream
|
||||
.strip_prefix("http://")
|
||||
.expect("fixture Vault must use HTTP")
|
||||
.trim_end_matches('/')
|
||||
.to_owned();
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.expect("proxy bind");
|
||||
let address = listener.local_addr().expect("proxy address");
|
||||
let (applied_tx, applied_rx) = oneshot::channel();
|
||||
let (release_tx, release_rx) = oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let (mut client, _) = listener.accept().await.expect("proxy accept");
|
||||
let request = read_http_message(&mut client).await.expect("proxy request");
|
||||
let mut vault = TcpStream::connect(&authority)
|
||||
.await
|
||||
.expect("connect fixture Vault");
|
||||
vault.write_all(&request).await.expect("forward request");
|
||||
let response = read_http_message(&mut vault).await.expect("Vault response");
|
||||
let _ = applied_tx.send(());
|
||||
let _ = release_rx.await;
|
||||
client.write_all(&response).await.expect("release response");
|
||||
});
|
||||
(format!("http://{address}"), applied_rx, release_tx)
|
||||
}
|
||||
|
||||
async fn read_http_message(stream: &mut TcpStream) -> io::Result<Vec<u8>> {
|
||||
let mut message = Vec::with_capacity(8192);
|
||||
let mut chunk = [0u8; 4096];
|
||||
let mut expected = None;
|
||||
loop {
|
||||
let read = stream.read(&mut chunk).await?;
|
||||
if read == 0 {
|
||||
return Ok(message);
|
||||
}
|
||||
message.extend_from_slice(&chunk[..read]);
|
||||
if expected.is_none()
|
||||
&& let Some(header_end) = message.windows(4).position(|window| window == b"\r\n\r\n")
|
||||
{
|
||||
let headers = std::str::from_utf8(&message[..header_end])
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
let content_length = headers
|
||||
.lines()
|
||||
.find_map(|line| {
|
||||
let (name, value) = line.split_once(':')?;
|
||||
name.eq_ignore_ascii_case("content-length")
|
||||
.then(|| value.trim().parse::<usize>().ok())
|
||||
.flatten()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
expected = Some(header_end + 4 + content_length);
|
||||
}
|
||||
if expected.is_some_and(|length| message.len() >= length) {
|
||||
return Ok(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_pin_and_exact_version_resolution() {
|
||||
let Some((pool, registry, resolver)) = harness().await else {
|
||||
|
|
@ -1075,103 +1013,6 @@ async fn reconcile_claim_blocks_concurrent_materialize() {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent_reconcilers_claim_before_a_late_create_response() {
|
||||
let Some(pool) = test_pool().await else {
|
||||
return;
|
||||
};
|
||||
let Some(direct_vault) = vault_client() else {
|
||||
return;
|
||||
};
|
||||
let upstream = direct_vault.config().address.clone();
|
||||
let (proxy_address, applied, release) = held_vault_response_proxy(&upstream).await;
|
||||
let mut delayed_config = VaultConfig::from_env().expect("Vault config");
|
||||
delayed_config.address = proxy_address;
|
||||
delayed_config.timeout = Duration::from_secs(5);
|
||||
let delayed_registry = CredentialRegistry::new(
|
||||
pool.clone(),
|
||||
VaultClient::new(delayed_config).expect("delayed Vault client"),
|
||||
)
|
||||
.with_reconcile_min_age(Duration::ZERO);
|
||||
let reconciler = Arc::new(
|
||||
CredentialRegistry::new(pool.clone(), direct_vault.clone())
|
||||
.with_reconcile_min_age(Duration::ZERO),
|
||||
);
|
||||
let owner = owner("late-response");
|
||||
let create_owner = owner.clone();
|
||||
let create_task = tokio::spawn(async move {
|
||||
delayed_registry
|
||||
.create(
|
||||
&create_owner,
|
||||
CreateCredential {
|
||||
label: "late-response".into(),
|
||||
material: token_material("secret-late-response"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
tokio::time::timeout(Duration::from_secs(5), applied)
|
||||
.await
|
||||
.expect("Vault apply timeout")
|
||||
.expect("Vault apply signal");
|
||||
let (intent_id, secret_mount, secret_path): (String, String, String) = sqlx::query_as(
|
||||
"SELECT intent_id, secret_mount, secret_path
|
||||
FROM secret_write_intents
|
||||
WHERE operation = 'create' AND owner_id = $1
|
||||
ORDER BY created_at DESC LIMIT 1",
|
||||
)
|
||||
.bind(owner.as_str())
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.expect("prepared create intent");
|
||||
|
||||
let barrier = Arc::new(Barrier::new(4));
|
||||
let mut reconcile_tasks = Vec::new();
|
||||
for _ in 0..4 {
|
||||
let reconciler = Arc::clone(&reconciler);
|
||||
let barrier = Arc::clone(&barrier);
|
||||
reconcile_tasks.push(tokio::spawn(async move {
|
||||
barrier.wait().await;
|
||||
reconciler.reconcile_secret_write_intents().await
|
||||
}));
|
||||
}
|
||||
for task in reconcile_tasks {
|
||||
task.await.expect("reconciler join").expect("reconcile");
|
||||
}
|
||||
|
||||
let status: String =
|
||||
sqlx::query_scalar("SELECT status FROM secret_write_intents WHERE intent_id = $1")
|
||||
.bind(&intent_id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.expect("intent status");
|
||||
assert_eq!(status, "abandoned");
|
||||
|
||||
release.send(()).expect("release Vault response");
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_secs(5), create_task)
|
||||
.await
|
||||
.expect("create completion timeout")
|
||||
.expect("create join")
|
||||
.is_err()
|
||||
);
|
||||
let credential_count: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM source_credentials WHERE owner_id = $1")
|
||||
.bind(owner.as_str())
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.expect("credential count");
|
||||
assert_eq!(credential_count, 0);
|
||||
assert!(
|
||||
direct_vault
|
||||
.list_live_versions(&secret_mount, &secret_path)
|
||||
.await
|
||||
.expect("final Vault metadata")
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepared_intent_fence_blocks_a_late_exact_path_write() {
|
||||
let Some((pool, registry, _)) = harness().await else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue