1 //! `SiGitCode` served in-process, the way an app hosts it, against the testkit's mock endpoint:
2 //! the method is advertised, a signed-out session is refused, a stored session reaches the
3 //! endpoint as the bearer, and signing out takes it away again.
4 //!
5 //! One test function: the sign-in reads process-wide environment variables.
6
7 use std::sync::Arc;
8 use std::sync::atomic::Ordering;
9
10 use agent_client_protocol::schema::ProtocolVersion;
11 use agent_client_protocol::schema::v1::{
12 ContentBlock, InitializeRequest, LogoutRequest, NewSessionRequest, PromptRequest, StopReason,
13 TextContent,
14 };
15 use agent_client_protocol::{Agent, Channel, Client, ConnectionTo};
16 use ed_acp::ServeOptions;
17 use ed_acp_testkit::{MockStats, Script, Step, start_mock_llm};
18 use sigit_agent::{METHOD_ID, SiGitCode, credentials};
19
20 #[tokio::test]
21 async fn the_profile_signs_in_through_its_provider_and_talks_to_the_cloud() {
22 let dir = std::env::temp_dir().join(format!("sigit_agent_embedded_{}", std::process::id()));
23 let _ = std::fs::remove_dir_all(&dir);
24 std::fs::create_dir_all(&dir).unwrap();
25
26 let stats = Arc::new(MockStats::default());
27 let url = start_mock_llm(
28 Script(vec![Step::Final("hello from the cloud")]),
29 stats.clone(),
30 )
31 .await;
32 // SAFETY: this is the only test in the binary, so nothing else reads the environment.
33 unsafe {
34 std::env::set_var("SIGIT_CONFIG_DIR", dir.join("config"));
35 std::env::set_var("SIGIT_AGENT_DATA_DIR", dir.join("data"));
36 std::env::set_var("SIGIT_CLOUD_URL", &url);
37 std::env::set_var("SIGIT_AGENT_MODEL", ed_acp_testkit::MODEL);
38 // The mock is the only server in play.
39 std::env::set_var("SIGIT_MCP_OFFICIAL", "off");
40 }
41 assert!(credentials::load().is_none());
42
43 let (agent_end, client_end) = Channel::duplex();
44 let opts = ServeOptions {
45 yolo: false,
46 surface: "app",
47 };
48 tokio::spawn(ed_acp::serve(
49 Arc::new(SiGitCode::without_shell()),
50 opts,
51 agent_end,
52 ));
53 let cwd = dir.clone();
54 let seen = stats.clone();
55
56 Client
57 .builder()
58 .connect_with(client_end, |conn: ConnectionTo<Agent>| async move {
59 let init = conn
60 .send_request(InitializeRequest::new(ProtocolVersion::V1))
61 .block_task()
62 .await?;
63 assert_eq!(init.auth_methods.len(), 1);
64 assert_eq!(&*init.auth_methods[0].id().0, METHOD_ID);
65 assert_eq!(init.agent_info.unwrap().name, "sigit-agent");
66
67 let refused = conn
68 .send_request(NewSessionRequest::new(cwd.clone()))
69 .block_task()
70 .await;
71 assert!(refused.is_err(), "signed out, so no session");
72
73 // What a finished browser sign-in leaves behind.
74 credentials::store(&credentials::Credentials {
75 // The mock accepts one bearer.
76 access_token: ed_acp_testkit::TEST_KEY.into(),
77 email: Some("dev@sigit.si".into()),
78 })
79 .unwrap();
80
81 let session = conn
82 .send_request(NewSessionRequest::new(cwd.clone()))
83 .block_task()
84 .await?;
85 let reply = conn
86 .send_request(PromptRequest::new(
87 session.session_id.clone(),
88 vec![ContentBlock::Text(TextContent::new("hi"))],
89 ))
90 .block_task()
91 .await?;
92 assert_eq!(reply.stop_reason, StopReason::EndTurn);
93 assert!(*seen.saw_bearer.lock().unwrap());
94 assert_eq!(seen.requests.load(Ordering::SeqCst), 1);
95
96 // The model saw siGit Code's prompt and no run_command.
97 let payload = seen.payloads.lock().unwrap()[0].clone();
98 let system = payload["messages"][0]["content"]
99 .as_str()
100 .unwrap()
101 .to_string();
102 assert!(system.contains("siGit"));
103 assert!(system.contains("cannot run commands"));
104 let tools = payload["tools"].to_string();
105 assert!(tools.contains("edit_file"));
106 assert!(!tools.contains("run_command"));
107
108 conn.send_request(LogoutRequest::new()).block_task().await?;
109 assert!(credentials::load().is_none());
110 let after = conn
111 .send_request(NewSessionRequest::new(cwd.clone()))
112 .block_task()
113 .await;
114 assert!(after.is_err(), "signed out again");
115 Ok(())
116 })
117 .await
118 .expect("conversation");
119
120 let _ = std::fs::remove_dir_all(&dir);
121 }