1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! v1.0.82 (GAP-005): integration tests for stderr capture and the
//! LLM fallback chain.
//!
//! v1.0.81 surfaced subprocess failures with a placeholder
//! `stderr={}` (literal). v1.0.82 captures the actual stderr tail
//! (UTF-8 safe, 1 KB max) into the error envelope so operators can
//! see WHY the subprocess failed (rate limit, OOM, OAuth error).
//!
//! The fallback chain (`embed_with_fallback` in `src/embedder.rs`)
//! tries `codex` first, then `claude`, then `None`. The unit tests
//! cover the chain mechanics; these integration tests verify the
//! CLI surface that exposes the chain via `--llm-fallback`.
#![cfg(feature = "slow-tests")]
use assert_cmd::Command;
use serial_test::serial;
use tempfile::TempDir;
#[path = "common/mod.rs"]
mod common;
fn cmd_base(tmp: &TempDir) -> Command {
// GAP-SG-101: product env is not read (G-T-XDG-04).
let mock_dir = common::mock_llm_path();
let mut c = Command::cargo_bin("sqlite-graphrag").expect("sqlite-graphrag binary not found");
c.env("PATH", common::prepend_path(&mock_dir));
common::wire_assert_cmd(tmp, &mut c, "test.sqlite");
c.arg("--skip-memory-guard");
c
}
/// Initialize the test database. The `pending_embeddings` queries
/// (V015) require the schema to be at version 13 or higher, which is
/// only applied by `init`.
fn init_db(tmp: &TempDir) {
cmd_base(tmp).arg("init").arg("--force").assert().success();
}
/// GAP-005 acceptance: the `embedding` subcommand exposes
/// `status` for inspecting the V015 `pending_embeddings` queue.
/// The response is JSON with `action: "embedding_status"` and
/// `pending`/`in_progress`/`done`/`abandoned` integer counters.
#[test]
#[serial]
fn embedding_status_returns_queue_counts() {
let tmp = TempDir::new().expect("tempdir");
init_db(&tmp);
let out = cmd_base(&tmp)
.arg("embedding")
.arg("status")
.output()
.expect("invoke");
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout must be valid JSON");
assert_eq!(parsed["action"], "embedding_status");
assert!(parsed["counts"]["pending"].is_number());
assert!(parsed["counts"]["in_progress"].is_number());
assert!(parsed["counts"]["done"].is_number());
assert!(parsed["counts"]["abandoned"].is_number());
}
/// GAP-005 acceptance: `pending-embeddings list --status
/// pending` returns the JSON envelope with `action: "pending_list"`
/// and an `entries` array. Empty array on a fresh database.
#[test]
#[serial]
fn pending_embeddings_list_returns_empty_on_fresh_db() {
let tmp = TempDir::new().expect("tempdir");
init_db(&tmp);
let out = cmd_base(&tmp)
.arg("pending-embeddings")
.arg("list")
.arg("--status")
.arg("pending")
.output()
.expect("invoke");
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("stdout must be valid JSON");
assert_eq!(parsed["action"], "pending_embeddings_list");
assert_eq!(parsed["filter_status"], "pending");
assert!(parsed["entries"].is_array());
assert_eq!(parsed["entries"].as_array().unwrap().len(), 0);
assert!(parsed["count"].is_number());
}
/// GAP-005 acceptance: `pending-embeddings list` rejects unknown
/// filter-status values with a validation error (exit 1). The
/// accepted values are `pending|in_progress|done|abandoned`.
#[test]
#[serial]
fn pending_embeddings_list_rejects_unknown_status() {
let tmp = TempDir::new().expect("tempdir");
init_db(&tmp);
cmd_base(&tmp)
.arg("pending-embeddings")
.arg("list")
.arg("--status")
.arg("not-a-status")
.assert()
.failure()
.code(1);
}