use crate::embedding::{EMBED_MODEL_ID, EMBEDDING_DIMS, EmbeddingEngine, MAX_EMBEDDING_TOKENS};
use crate::embedding_profiles::{EmbeddingRole, profile_for};
use crate::errors::Error;
use super::model_free::store_prefix_path::store_with_recorded_identity;
const EXPECTED_REQUIRES_TOKEN_TYPE_IDS_BGE: bool = true;
const EXPECTED_REQUIRES_TOKEN_TYPE_IDS_E5: bool = true;
#[ignore]
#[test]
fn test_requires_token_type_ids_matches_measured_baseline() {
let bge = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load bge model");
assert_eq!(
bge.requires_token_type_ids, EXPECTED_REQUIRES_TOKEN_TYPE_IDS_BGE,
"bge-small requires_token_type_ids drifted from the rc.9 measurement"
);
let e5 = EmbeddingEngine::new("intfloat/multilingual-e5-small").expect("load e5 model");
assert_eq!(
e5.requires_token_type_ids, EXPECTED_REQUIRES_TOKEN_TYPE_IDS_E5,
"multilingual-e5-small requires_token_type_ids drifted from the rc.9 measurement"
);
}
#[ignore]
#[test]
fn test_integration_whitespace_only() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let embedding = engine
.embed_passage(" \t\n ")
.expect("embed whitespace text");
assert_eq!(embedding.len(), EMBEDDING_DIMS);
assert!(embedding.iter().all(|&x| x.is_finite()));
}
#[ignore]
#[test]
fn test_integration_simple_text() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let embedding = engine.embed_passage("hello world").expect("embed text");
assert_eq!(embedding.len(), EMBEDDING_DIMS);
let norm: f32 = embedding.iter().map(|&x| x * x).sum::<f32>().sqrt();
assert!(
(norm - 1.0).abs() < 0.01,
"Embedding should be L2-normalized"
);
assert!(embedding.iter().all(|&x| x.is_finite()));
}
#[ignore]
#[test]
fn test_integration_empty_string() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let embedding = engine.embed_passage("").expect("embed empty text");
assert_eq!(embedding.len(), EMBEDDING_DIMS);
assert_eq!(embedding, vec![0.0f32; EMBEDDING_DIMS]);
let embedding = engine.embed_query("").expect("embed empty text (query)");
assert_eq!(embedding, vec![0.0f32; EMBEDDING_DIMS]);
}
#[ignore]
#[test]
fn test_integration_e5_output_norm_is_one() {
let profile = profile_for("intfloat/multilingual-e5-small").expect("e5 profile");
let mut engine = EmbeddingEngine::new(profile.model_id).expect("load e5 model");
let embedding = engine
.embed_passage("The quick brown fox jumps over the lazy dog.")
.expect("embed e5 text");
assert_eq!(
embedding.len(),
EMBEDDING_DIMS,
"e5 must produce 384-dim vectors"
);
let norm: f32 = embedding.iter().map(|&x| x * x).sum::<f32>().sqrt();
assert!(
(norm - 1.0).abs() < 0.01,
"e5 output must be L2-normalised (norm ≈ 1.0) so the Real band [0.99, 1.01] in classify_embedding holds; got {norm}"
);
assert!(
embedding.iter().all(|&x| x.is_finite()),
"e5 output must be all-finite"
);
}
fn recording_embedder_local(
recorded: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
) -> crate::memory::store::TestEmbedder {
Box::new(move |content: &str| {
recorded.lock().unwrap().push(content.to_string());
crate::memory::crud::test_fake_embedder(content)
})
}
#[ignore]
#[test]
fn test_integration_long_text_rejection() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let long_text = build_text_up_to_tokens(&engine, 600);
let actual_count = engine
.token_count(EmbeddingRole::Passage, &long_text)
.expect("count tokens");
assert!(
actual_count > MAX_EMBEDDING_TOKENS,
"Test setup: need >512 tokens, got {}",
actual_count
);
let result = engine.embed_passage(&long_text);
assert!(result.is_err());
match result.unwrap_err() {
Error::ContentTooLong {
token_count: tc,
max_tokens,
} => {
assert_eq!(tc, actual_count);
assert_eq!(max_tokens, MAX_EMBEDDING_TOKENS);
}
_ => panic!("Expected ContentTooLong error"),
}
}
fn build_text_up_to_tokens(engine: &EmbeddingEngine, target: usize) -> String {
let words = target;
let text = "word ".repeat(words);
let count = engine
.token_count(EmbeddingRole::Passage, &text)
.expect("count tokens");
if count <= target {
return text.trim().to_string();
}
let (mut lo, mut hi) = (0usize, words);
while lo < hi {
let mid = lo + (hi - lo).div_ceil(2);
let candidate = "word ".repeat(mid);
let c = engine
.token_count(EmbeddingRole::Passage, &candidate)
.expect("count tokens");
if c <= target {
lo = mid;
} else {
hi = mid - 1;
}
}
"word ".repeat(lo).trim().to_string()
}
fn run_boundary_test(
engine: &mut EmbeddingEngine,
target: usize,
expect_success: bool,
min_tokens: usize,
) {
let text = build_text_up_to_tokens(engine, target);
let actual_count = engine
.token_count(EmbeddingRole::Passage, &text)
.expect("count tokens");
assert!(
actual_count >= min_tokens,
"Expected >= {} tokens, got {}",
min_tokens,
actual_count
);
if expect_success {
let embedding = engine.embed_passage(&text).expect("embed text");
assert_eq!(embedding.len(), EMBEDDING_DIMS);
let norm: f32 = embedding.iter().map(|&x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 0.01);
} else {
let result = engine.embed_passage(&text);
assert!(result.is_err());
match result.unwrap_err() {
Error::ContentTooLong {
token_count: tc,
max_tokens,
} => {
assert_eq!(tc, actual_count);
assert_eq!(max_tokens, MAX_EMBEDDING_TOKENS);
}
_ => panic!("Expected ContentTooLong error"),
}
}
}
#[ignore]
#[test]
fn test_integration_boundary_512_tokens() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
run_boundary_test(&mut engine, 512, true, 1);
}
fn build_largest_at_most(engine: &mut EmbeddingEngine, limit: usize) -> (String, usize) {
let count_tokens = |n: usize| {
engine
.token_count(EmbeddingRole::Passage, "word ".repeat(n).trim())
.expect("count tokens")
};
let mut lo = 0usize;
let mut hi = limit;
while lo < hi {
let mid = lo + (hi - lo).div_ceil(2);
if count_tokens(mid) <= limit {
lo = mid;
} else {
hi = mid - 1;
}
}
let text = "word ".repeat(lo).trim().to_string();
let actual = count_tokens(lo);
assert!(
actual <= limit,
"Expected <= {} tokens, got {}",
limit,
actual
);
(text, actual)
}
#[ignore]
#[test]
fn test_integration_boundary_511_tokens() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let (text, count) = build_largest_at_most(&mut engine, MAX_EMBEDDING_TOKENS - 1);
assert!(
count < MAX_EMBEDDING_TOKENS,
"Expected < {} tokens, got {}",
MAX_EMBEDDING_TOKENS,
count
);
let embedding = engine
.embed_passage(&text)
.expect("embed one-token-under text");
assert_eq!(embedding.len(), EMBEDDING_DIMS);
}
#[ignore]
#[test]
fn test_integration_boundary_513_tokens() {
let mut engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
run_boundary_test(&mut engine, 520, false, MAX_EMBEDDING_TOKENS + 1);
}
#[ignore]
#[test]
fn test_token_count_method() {
let engine = EmbeddingEngine::new(EMBED_MODEL_ID).expect("load model");
let text = "hello world";
let passage_count = engine
.token_count(EmbeddingRole::Passage, text)
.expect("count tokens");
assert!(passage_count > 0);
assert!(passage_count <= MAX_EMBEDDING_TOKENS);
let query_count = engine
.token_count(EmbeddingRole::Query, text)
.expect("count tokens");
assert_eq!(
passage_count, query_count,
"bge prefixes are empty: counts must agree"
);
}
#[ignore]
#[test]
fn test_e5_embed_passage_input_is_prefix_plus_text() {
let profile = crate::embedding_profiles::profile_for("intfloat/multilingual-e5-small")
.expect("e5 profile");
let mut engine = EmbeddingEngine::new(profile.model_id).expect("load e5 model");
let text = "The quick brown fox jumps over the lazy dog.";
let expected_input = "passage: The quick brown fox jumps over the lazy dog.";
let (_dir, mut store) = store_with_recorded_identity(profile.model_id);
let recorded = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
store.set_test_embedder(recording_embedder_local(recorded.clone()));
let result = store
.add_with_conflict(
"p",
text,
None,
false,
crate::memory::lifecycle::MemoryType::Fact,
crate::memory::lifecycle::MemoryStatus::Active,
)
.expect("add must succeed");
let crate::memory_types::AddResult::Added { id: _ } = result else {
panic!("expected Added");
};
let engine_input = recorded.lock().unwrap().clone().pop().expect("one embed");
assert_eq!(
engine_input, expected_input,
"v0.13.0's get_embedding handed the engine exactly 'passage: x'"
);
engine.embed_passage(text).expect("embed e5 text");
}