返回 CodeWhale
workspace_instructions.rs
根目录 / crates / tui / src / runtime_api / tests / workspace_instructions.rs
1 use super::*;
2
3 #[tokio::test]
4 async fn get_v1_workspace_instructions_lists_effective_sources() -> Result<()> {
5 // No `lock_test_env`/`EnvVarGuard` here: the handler resolves the global
6 // layer's home inside `spawn_blocking`, and a blocking-pool thread is not
7 // enrolled in the test's env scope — sealing the environment would
8 // deadlock the request against the lock this test would be holding.
9 let temp = tempfile::tempdir()?;
10 let root = temp.path().join("instructions-route");
11 let sessions_dir = root.join("sessions");
12 let workspace = root.join("workspace");
13 fs::create_dir_all(workspace.join(".git"))?;
14 fs::write(
15 workspace.join(".git").join("HEAD"),
16 "ref: refs/heads/main\n",
17 )?;
18 fs::write(workspace.join("AGENTS.md"), "# Repo rules\n")?;
19 // Exists but shadowed: AGENTS.md outranks it in the same directory.
20 fs::create_dir_all(workspace.join(".codewhale"))?;
21 fs::write(
22 workspace.join(".codewhale").join("instructions.md"),
23 "shadowed\n",
24 )?;
25 fs::create_dir_all(workspace.join(".codewhale").join("rules"))?;
26 fs::write(
27 workspace.join(".codewhale").join("rules").join("style.md"),
28 "keep it small\n",
29 )?;
30
31 let Some((addr, _runtime_threads, handle)) =
32 spawn_test_server_with_root_token_mobile_workspace(
33 root,
34 sessions_dir,
35 None,
36 false,
37 workspace.clone(),
38 )
39 .await?
40 else {
41 return Ok(());
42 };
43 let client = crate::tls::reqwest_client();
44
45 let body: serde_json::Value = client
46 .get(format!("http://{addr}/v1/workspace/instructions"))
47 .send()
48 .await?
49 .error_for_status()?
50 .json()
51 .await?;
52 let sources = body["sources"].as_array().expect("sources array");
53
54 let agents = sources
55 .iter()
56 .find(|source| {
57 source["path"]
58 .as_str()
59 .is_some_and(|p| p.ends_with("AGENTS.md"))
60 })
61 .expect("AGENTS.md row");
62 assert_eq!(agents["kind"], "project");
63 assert_eq!(agents["status"], "loaded");
64 assert_eq!(agents["relative_path"], "AGENTS.md");
65 assert_eq!(agents["exists"], true);
66 assert!(agents["bytes"].as_u64().is_some());
67
68 let shadowed = sources
69 .iter()
70 .find(|source| {
71 source["path"]
72 .as_str()
73 // `path` is the native absolute path, so it is backslashed on
74 // Windows; compare separator-agnostically rather than against
75 // one platform's spelling.
76 .is_some_and(|p| p.replace('\\', "/").ends_with(".codewhale/instructions.md"))
77 })
78 .expect("shadowed workspace instructions row");
79 assert_eq!(shadowed["status"], "shadowed");
80
81 let rule = sources
82 .iter()
83 .find(|source| source["kind"] == "rule")
84 .expect("rules file row");
85 assert_eq!(rule["status"], "loaded");
86 assert_eq!(rule["relative_path"], ".codewhale/rules/style.md");
87
88 // Unchecked-but-real candidates are reported too, so clients can render
89 // the full precedence map instead of a post-hoc file scan.
90 assert!(
91 sources.iter().any(|source| source["status"] == "missing"),
92 "missing candidates must be listed: {sources:?}"
93 );
94
95 assert_eq!(body["generated_fallback"], false);
96 assert!(body["aggregate_budget_bytes"].as_u64().unwrap() > 0);
97
98 handle.abort();
99 Ok(())
100 }
101
101 lines RUST