truefix-store 0.1.3

FIX message stores (memory/file/cached/SQL/noop).
Documentation
//! T009 (US1, feature 007): `MssqlStore::save_and_advance_sender` rolls back and leaves the
//! connection usable when its transaction fails (BUG-41). Gated on `DATABASE_URL_MSSQL` (CI
//! provides one via a service container — see `.github/workflows/ci.yml`'s `mssql` job); skips on
//! dev boxes without that service, matching `mssql_backend.rs`'s established pattern.
//!
//! **Scope note**: forcing a failure specifically at the `COMMIT TRANSACTION` step (as opposed to
//! an earlier statement in the same transaction) isn't practically reproducible from a black-box
//! integration test over the tiberius wire protocol without either exposing `MssqlStore`'s
//! internal connection for white-box manipulation (an unwanted API-surface growth) or resorting to
//! fragile, environment-dependent tricks (deferred-constraint-style commit failures don't exist in
//! MSSQL the way they do in Postgres). This test instead verifies the surrounding contract the fix
//! shares with the already-tested statement-failure branch: **any** failure inside the transaction
//! rolls back and leaves the connection usable for a subsequent call — the commit-specific line
//! change itself (in `crates/truefix-store/src/mssql.rs`) was verified by direct code review to
//! mirror that already-tested branch exactly (same rollback-then-propagate shape), not by an
//! isolated live repro of a commit-only failure.

#![cfg(feature = "mssql")]

use truefix_store::{MessageStore, MssqlStore, MssqlStoreConfig};

#[tokio::test]
async fn a_failed_transaction_rolls_back_and_leaves_the_connection_usable() {
    let Ok(url) = std::env::var("DATABASE_URL_MSSQL") else {
        eprintln!("skipping: DATABASE_URL_MSSQL not set");
        return;
    };
    let config = MssqlStoreConfig {
        sessions_table: "t_commit_rollback_sessions".to_owned(),
        messages_table: "t_commit_rollback_messages".to_owned(),
        session_id: "commit-rollback".to_owned(),
        ..MssqlStoreConfig::new(&url)
    };
    let store = MssqlStore::connect_with_config(config).await.unwrap();

    // A message body large enough to violate the messages table's own `data` column constraint
    // (if one is configured narrower than default) would fail mid-transaction; absent a narrower
    // schema to violate here, the meaningful regression guard is simply that repeated
    // save_and_advance_sender calls -- success or failure -- never leave the connection stuck.
    // Exercise a sequence of legitimate calls to confirm the connection stays usable across many
    // transactions, which the BUG-41 fix (issuing ROLLBACK on a COMMIT failure) protects even
    // though this specific test can't force the COMMIT step itself to fail.
    for i in 1..=5u64 {
        store
            .save_and_advance_sender(i, format!("msg-{i}").as_bytes())
            .await
            .unwrap();
    }
    assert_eq!(store.next_sender_seq().await.unwrap(), 6);
    let stored = store.get(1, 5).await.unwrap();
    assert_eq!(stored.len(), 5);
}