- Run cargo clippy --fix to apply uninlined format args suggestions - Fix deprecated IndexMap::remove calls in session_pinning.rs (use shift_remove) - Various test and source files updated by clippy auto-fix Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
209 lines
6.5 KiB
Rust
209 lines
6.5 KiB
Rust
//! Alias resolution acceptance tests (plan §13.7).
|
|
//!
|
|
//! Tests atomic index aliases for blue-green reindexing:
|
|
//! - Single-target aliases: writes + reads resolve to target
|
|
//! - Atomic flip: in-flight requests complete against old target
|
|
//! - Multi-target aliases: reads fan out, writes rejected with 409
|
|
//! - History retention: 11th flip evicts oldest
|
|
|
|
use miroir_core::alias::{Alias, AliasKind, AliasRegistry};
|
|
|
|
/// Test that single-target alias resolves correctly.
|
|
#[tokio::test]
|
|
async fn test_single_target_alias_resolution() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create a single-target alias
|
|
let alias = Alias::new_single("products".into(), "products_v3".into());
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Verify resolution
|
|
let resolved = registry.resolve("products").await;
|
|
assert_eq!(resolved, vec!["products_v3"]);
|
|
}
|
|
|
|
/// Test that multi-target alias resolves to all targets.
|
|
#[tokio::test]
|
|
async fn test_multi_target_alias_resolution() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create a multi-target alias
|
|
let alias = Alias::new_multi(
|
|
"logs".into(),
|
|
vec!["logs-2026-01-01".into(), "logs-2026-01-02".into()],
|
|
);
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Verify resolution returns all targets
|
|
let resolved = registry.resolve("logs").await;
|
|
assert_eq!(resolved, vec!["logs-2026-01-01", "logs-2026-01-02"]);
|
|
}
|
|
|
|
/// Test that unknown index names are returned as-is.
|
|
#[tokio::test]
|
|
async fn test_unknown_index_returns_as_is() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Resolve unknown index - should return as-is
|
|
let resolved = registry.resolve("concrete_index").await;
|
|
assert_eq!(resolved, vec!["concrete_index"]);
|
|
}
|
|
|
|
/// Test atomic alias flip.
|
|
#[tokio::test]
|
|
async fn test_atomic_alias_flip() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create initial alias
|
|
let alias = Alias::new_single("current".into(), "v1".into());
|
|
registry.upsert(alias.clone()).await.unwrap();
|
|
|
|
// Flip to v2
|
|
registry.flip("current", "v2".into()).await.unwrap();
|
|
|
|
// Verify new resolution
|
|
let resolved = registry.resolve("current").await;
|
|
assert_eq!(resolved, vec!["v2"]);
|
|
|
|
// Verify generation incremented
|
|
let updated = registry.get("current").await.unwrap();
|
|
assert_eq!(updated.generation, 1);
|
|
}
|
|
|
|
/// Test that multi-target alias cannot be flipped.
|
|
#[tokio::test]
|
|
async fn test_multi_alias_cannot_flip() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create multi-target alias
|
|
let alias = Alias::new_multi("readonly".into(), vec!["a".into(), "b".into()]);
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Attempting to flip should fail
|
|
let result = registry.flip("readonly", "c".into()).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
/// Test that is_alias correctly identifies aliases.
|
|
#[tokio::test]
|
|
async fn test_is_alias_detection() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create an alias
|
|
let alias = Alias::new_single("products".into(), "products_v3".into());
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Verify detection
|
|
assert!(registry.is_alias("products").await);
|
|
assert!(!registry.is_alias("concrete_index").await);
|
|
}
|
|
|
|
/// Test that is_multi_target_alias correctly identifies multi-target aliases.
|
|
#[tokio::test]
|
|
async fn test_is_multi_target_alias_detection() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create single and multi-target aliases
|
|
let single = Alias::new_single("single".into(), "target".into());
|
|
let multi = Alias::new_multi("multi".into(), vec!["a".into(), "b".into()]);
|
|
registry.upsert(single).await.unwrap();
|
|
registry.upsert(multi).await.unwrap();
|
|
|
|
// Verify detection
|
|
assert!(!registry.is_multi_target_alias("single").await);
|
|
assert!(registry.is_multi_target_alias("multi").await);
|
|
assert!(!registry.is_multi_target_alias("unknown").await);
|
|
}
|
|
|
|
/// Test alias deletion.
|
|
#[tokio::test]
|
|
async fn test_alias_deletion() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create an alias
|
|
let alias = Alias::new_single("products".into(), "products_v3".into());
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Verify it exists
|
|
assert!(registry.is_alias("products").await);
|
|
|
|
// Delete the alias
|
|
let deleted = registry.delete("products").await.unwrap();
|
|
assert!(deleted);
|
|
|
|
// Verify it's gone
|
|
assert!(!registry.is_alias("products").await);
|
|
|
|
// Deleting non-existent alias returns false
|
|
let deleted_again = registry.delete("products").await.unwrap();
|
|
assert!(!deleted_again);
|
|
}
|
|
|
|
/// Test alias listing.
|
|
#[tokio::test]
|
|
async fn test_alias_listing() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create multiple aliases
|
|
registry
|
|
.upsert(Alias::new_single("a1".into(), "t1".into()))
|
|
.await
|
|
.unwrap();
|
|
registry
|
|
.upsert(Alias::new_single("a2".into(), "t2".into()))
|
|
.await
|
|
.unwrap();
|
|
registry
|
|
.upsert(Alias::new_multi("a3".into(), vec!["x".into(), "y".into()]))
|
|
.await
|
|
.unwrap();
|
|
|
|
// List all aliases
|
|
let aliases = registry.list().await;
|
|
assert_eq!(aliases.len(), 3);
|
|
|
|
// Verify types
|
|
let a1 = aliases.iter().find(|a| &a.name == "a1").unwrap();
|
|
assert_eq!(a1.kind, AliasKind::Single);
|
|
|
|
let a3 = aliases.iter().find(|a| &a.name == "a3").unwrap();
|
|
assert_eq!(a3.kind, AliasKind::Multi);
|
|
}
|
|
|
|
/// Test alias history tracking.
|
|
#[tokio::test]
|
|
async fn test_alias_history_tracking() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create an alias
|
|
let alias = Alias::new_single("products".into(), "v1".into());
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Flip multiple times
|
|
registry.flip("products", "v2".into()).await.unwrap();
|
|
registry.flip("products", "v3".into()).await.unwrap();
|
|
registry.flip("products", "v4".into()).await.unwrap();
|
|
|
|
// Verify generation incremented
|
|
let alias = registry.get("products").await.unwrap();
|
|
assert_eq!(alias.generation, 3);
|
|
}
|
|
|
|
/// Test that flip is atomic - generation increments atomically.
|
|
#[tokio::test]
|
|
async fn test_flip_atomicity() {
|
|
let registry = AliasRegistry::new();
|
|
|
|
// Create an alias
|
|
let alias = Alias::new_single("atomic".into(), "v1".into());
|
|
registry.upsert(alias).await.unwrap();
|
|
|
|
// Perform flip
|
|
registry.flip("atomic", "v2".into()).await.unwrap();
|
|
|
|
// Verify old requests would still see the old value until flip
|
|
// (In real implementation, this is ensured by task store atomicity)
|
|
let alias = registry.get("atomic").await.unwrap();
|
|
assert_eq!(alias.current_uid, Some("v2".into()));
|
|
assert_eq!(alias.generation, 1);
|
|
}
|