refactor: extract unit tests into separate _tests.rs files

Move inline #[cfg(test)] mod tests { ... } blocks from 22 source files
into dedicated _tests.rs companion files, wired via:

    #[cfg(test)]
    #[path = "module_tests.rs"]
    mod tests;

This keeps implementation-focused source files leaner and more scannable
while preserving full access to private items through `use super::*;`.

Modules extracted:
  core:      db, note_parser, payloads, project, references, sync_run,
             timeline_collect, timeline_expand, timeline_seed
  cli:       list (55 tests), who (75 tests)
  documents: extractor (43 tests), regenerator
  embedding: change_detector, chunking
  gitlab:    graphql (wiremock async tests), transformers/issue
  ingestion: dirty_tracker, discussions, issues, mr_diffs

Also adds conflicts_with("explain_score") to the --detail flag in the
who command to prevent mutually exclusive flags from being combined.

All 629 unit tests pass. No behavior changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-02-13 10:54:02 -05:00
parent 5c2df3df3b
commit 7e0e6a91f2
43 changed files with 11672 additions and 11942 deletions

View File

@@ -85,146 +85,5 @@ pub fn count_pending_documents(conn: &Connection, model_name: &str) -> Result<i6
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
use crate::core::db::{create_connection, run_migrations};
use crate::embedding::pipeline::record_embedding_error;
const MODEL: &str = "nomic-embed-text";
fn setup_db() -> Connection {
let conn = create_connection(Path::new(":memory:")).unwrap();
run_migrations(&conn).unwrap();
conn
}
fn insert_test_project(conn: &Connection) -> i64 {
conn.execute(
"INSERT INTO projects (gitlab_project_id, path_with_namespace, web_url)
VALUES (1, 'group/test', 'https://gitlab.example.com/group/test')",
[],
)
.unwrap();
conn.last_insert_rowid()
}
fn insert_test_document(conn: &Connection, project_id: i64, content: &str) -> i64 {
conn.execute(
"INSERT INTO documents (source_type, source_id, project_id, content_text, content_hash)
VALUES ('issue', 1, ?1, ?2, 'hash123')",
rusqlite::params![project_id, content],
)
.unwrap();
conn.last_insert_rowid()
}
#[test]
fn retry_failed_delete_makes_doc_pending_again() {
let conn = setup_db();
let proj_id = insert_test_project(&conn);
let doc_id = insert_test_document(&conn, proj_id, "some text content");
// Doc starts as pending
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert_eq!(pending.len(), 1, "Doc should be pending initially");
// Record an error — doc should no longer be pending
record_embedding_error(
&conn,
doc_id,
0,
"hash123",
"chunkhash",
MODEL,
"test error",
)
.unwrap();
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert!(
pending.is_empty(),
"Doc with error metadata should not be pending"
);
// DELETE error rows (mimicking --retry-failed) — doc should become pending again
conn.execute_batch(
"DELETE FROM embeddings WHERE rowid / 1000 IN (
SELECT DISTINCT document_id FROM embedding_metadata
WHERE last_error IS NOT NULL
);
DELETE FROM embedding_metadata WHERE last_error IS NOT NULL;",
)
.unwrap();
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert_eq!(pending.len(), 1, "Doc should be pending again after DELETE");
assert_eq!(pending[0].document_id, doc_id);
}
#[test]
fn empty_doc_with_error_not_pending() {
let conn = setup_db();
let proj_id = insert_test_project(&conn);
let doc_id = insert_test_document(&conn, proj_id, "");
// Empty doc starts as pending
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert_eq!(pending.len(), 1, "Empty doc should be pending initially");
// Record an error for the empty doc
record_embedding_error(
&conn,
doc_id,
0,
"hash123",
"empty",
MODEL,
"Document has empty content",
)
.unwrap();
// Should no longer be pending
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert!(
pending.is_empty(),
"Empty doc with error metadata should not be pending"
);
}
#[test]
fn old_update_approach_leaves_doc_invisible() {
// This test demonstrates WHY we use DELETE instead of UPDATE.
// UPDATE clears last_error but the row still matches config params,
// so the doc stays "not pending" — permanently invisible.
let conn = setup_db();
let proj_id = insert_test_project(&conn);
let doc_id = insert_test_document(&conn, proj_id, "some text content");
// Record an error
record_embedding_error(
&conn,
doc_id,
0,
"hash123",
"chunkhash",
MODEL,
"test error",
)
.unwrap();
// Old approach: UPDATE to clear error
conn.execute(
"UPDATE embedding_metadata SET last_error = NULL, attempt_count = 0
WHERE last_error IS NOT NULL",
[],
)
.unwrap();
// Doc is NOT pending — it's permanently invisible! This is the bug.
let pending = find_pending_documents(&conn, 100, 0, MODEL).unwrap();
assert!(
pending.is_empty(),
"UPDATE approach leaves doc invisible (this proves the bug)"
);
}
}
#[path = "change_detector_tests.rs"]
mod tests;