use assert_cmd::Command;
use rusqlite::Connection;
use serial_test::serial;
use tempfile::TempDir;
fn init_db_isolado() -> (TempDir, std::path::PathBuf) {
let tmp = TempDir::new().expect("TempDir deve ser criado");
let db_path = tmp.path().join("test.sqlite");
Command::cargo_bin("sqlite-graphrag")
.expect("binário sqlite-graphrag não encontrado")
.env("SQLITE_GRAPHRAG_DB_PATH", &db_path)
.env("SQLITE_GRAPHRAG_CACHE_DIR", tmp.path())
.args(["--skip-memory-guard", "init"])
.assert()
.success();
(tmp, db_path)
}
fn conn_ro(db_path: &std::path::Path) -> Connection {
Connection::open(db_path).expect("conexão ao banco deve funcionar")
}
fn tabela_existe(conn: &Connection, nome: &str) -> bool {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type IN ('table','view') AND name = ?1",
rusqlite::params![nome],
|row| row.get(0),
)
.unwrap_or(0);
count > 0
}
fn trigger_existe(conn: &Connection, nome: &str) -> bool {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger' AND name = ?1",
rusqlite::params![nome],
|row| row.get(0),
)
.unwrap_or(0);
count > 0
}
fn indice_existe(conn: &Connection, nome: &str) -> bool {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = ?1",
rusqlite::params![nome],
|row| row.get(0),
)
.unwrap_or(0);
count > 0
}
#[test]
#[serial]
fn init_cria_5_migrations_v001_a_v005() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let versoes: Vec<i64> = {
let mut stmt = conn
.prepare("SELECT version FROM refinery_schema_history ORDER BY version ASC")
.expect("prepare deve funcionar");
stmt.query_map([], |row| row.get(0))
.expect("query deve funcionar")
.map(|r| r.expect("row deve ser lida"))
.collect()
};
assert_eq!(
versoes.len(),
5,
"deve haver exatamente 5 migrations aplicadas, encontrou: {versoes:?}"
);
assert_eq!(versoes, vec![1, 2, 3, 4, 5], "versões V001-V005 esperadas");
}
#[test]
#[serial]
fn trigger_trg_fts_ai_existe() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
assert!(
trigger_existe(&conn, "trg_fts_ai"),
"trigger trg_fts_ai deve existir após V004"
);
}
#[test]
#[serial]
fn trigger_trg_fts_ad_existe() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
assert!(
trigger_existe(&conn, "trg_fts_ad"),
"trigger trg_fts_ad deve existir após V004"
);
}
#[test]
#[serial]
fn trigger_trg_fts_au_ausente_conflito_vec() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
assert!(
!trigger_existe(&conn, "trg_fts_au"),
"trigger trg_fts_au NÃO deve existir — sqlite-vec conflita com FTS5 em AFTER UPDATE"
);
}
#[test]
#[serial]
fn vec_memories_dim_384_cosine() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let ddl: String = conn
.query_row(
"SELECT sql FROM sqlite_master WHERE name = 'vec_memories'",
[],
|row| row.get(0),
)
.expect("vec_memories deve existir no sqlite_master");
assert!(
ddl.contains("float[384]"),
"vec_memories deve declarar float[384], DDL obtido: {ddl}"
);
assert!(
ddl.contains("distance_metric=cosine"),
"vec_memories deve usar distance_metric=cosine, DDL obtido: {ddl}"
);
}
#[test]
#[serial]
fn vec_memories_partition_keys_namespace_type() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let ddl: String = conn
.query_row(
"SELECT sql FROM sqlite_master WHERE name = 'vec_memories'",
[],
|row| row.get(0),
)
.expect("vec_memories deve existir no sqlite_master");
let namespace_pk = ddl.contains("namespace") && ddl.to_lowercase().contains("partition key");
let type_pk = ddl.contains("type") && ddl.to_lowercase().contains("partition key");
assert!(
namespace_pk,
"vec_memories deve ter 'namespace' como partition key, DDL: {ddl}"
);
assert!(
type_pk,
"vec_memories deve ter 'type' como partition key, DDL: {ddl}"
);
}
#[test]
#[serial]
fn fts_memories_tokenizer_unicode61_remove_diacritics() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let ddl: String = conn
.query_row(
"SELECT sql FROM sqlite_master WHERE name = 'fts_memories'",
[],
|row| row.get(0),
)
.expect("fts_memories deve existir no sqlite_master");
assert!(
ddl.contains("unicode61"),
"fts_memories deve usar tokenizer unicode61, DDL: {ddl}"
);
assert!(
ddl.contains("remove_diacritics"),
"fts_memories deve declarar remove_diacritics, DDL: {ddl}"
);
}
#[test]
#[serial]
fn fts5_matching_com_acentos_cafe_cafe() {
let tmp = TempDir::new().expect("TempDir deve ser criado");
let db_path = tmp.path().join("test.sqlite");
Command::cargo_bin("sqlite-graphrag")
.expect("binário não encontrado")
.env("SQLITE_GRAPHRAG_DB_PATH", &db_path)
.env("SQLITE_GRAPHRAG_CACHE_DIR", tmp.path())
.args(["--skip-memory-guard", "init"])
.assert()
.success();
Command::cargo_bin("sqlite-graphrag")
.expect("binário não encontrado")
.env("SQLITE_GRAPHRAG_DB_PATH", &db_path)
.env("SQLITE_GRAPHRAG_CACHE_DIR", tmp.path())
.env("SQLITE_GRAPHRAG_NAMESPACE", "global")
.args([
"--skip-memory-guard",
"remember",
"--name",
"nota-cafe",
"--type",
"user",
"--description",
"nota sobre café",
"--body",
"O café brasileiro é famoso mundialmente por sua qualidade",
])
.assert()
.success();
let conn = conn_ro(&db_path);
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM fts_memories WHERE fts_memories MATCH 'cafe'",
[],
|row| row.get(0),
)
.expect("query FTS5 deve funcionar");
assert!(
count >= 1,
"FTS5 com remove_diacritics deve encontrar 'café' ao buscar 'cafe', count={count}"
);
}
#[test]
#[serial]
fn todas_tabelas_principais_existem_apos_init() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let tabelas = [
"schema_meta",
"memories",
"memory_versions",
"memory_chunks",
"entities",
"relationships",
"memory_entities",
"memory_relationships",
"fts_memories",
];
for nome in tabelas {
assert!(
tabela_existe(&conn, nome),
"tabela '{nome}' deve existir após init"
);
}
}
#[test]
#[serial]
fn indices_principais_existem_apos_init() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let indices = [
"idx_memories_ns_type",
"idx_memories_ns_live",
"idx_memories_body_hash",
"idx_entities_ns",
"idx_me_entity",
"idx_relationships_source_id",
"idx_relationships_target_id",
"idx_relationships_namespace_relation",
"idx_entities_namespace_degree",
"idx_memory_chunks_memory_id",
"idx_memory_relationships_relationship_id",
];
for nome in indices {
assert!(
indice_existe(&conn, nome),
"índice '{nome}' deve existir após init"
);
}
}
#[test]
#[serial]
fn schema_meta_campos_obrigatorios_existem() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let chaves_esperadas = ["schema_version", "model", "dim", "created_at"];
for chave in chaves_esperadas {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM schema_meta WHERE key = ?1",
rusqlite::params![chave],
|row| row.get(0),
)
.expect("query schema_meta deve funcionar");
assert!(
count > 0,
"schema_meta deve conter chave '{chave}' após init"
);
}
}
#[test]
#[serial]
fn schema_version_meta_igual_a_5() {
let (_tmp, db_path) = init_db_isolado();
let conn = conn_ro(&db_path);
let versao: String = conn
.query_row(
"SELECT value FROM schema_meta WHERE key = 'schema_version'",
[],
|row| row.get(0),
)
.expect("schema_version deve existir em schema_meta");
assert_eq!(
versao, "5",
"schema_version em schema_meta deve ser '5' após V005"
);
}