opencode_sdk/http/
questions.rs1use crate::error::Result;
6use crate::http::HttpClient;
7use crate::types::question::{QuestionReplyRequest, QuestionRequest};
8use reqwest::Method;
9
10#[derive(Clone)]
12pub struct QuestionsApi {
13 http: HttpClient,
14}
15
16impl QuestionsApi {
17 pub fn new(http: HttpClient) -> Self {
19 Self { http }
20 }
21
22 pub async fn list(&self) -> Result<Vec<QuestionRequest>> {
28 self.http.request_json(Method::GET, "/question", None).await
29 }
30
31 pub async fn reply(&self, request_id: &str, req: &QuestionReplyRequest) -> Result<bool> {
39 let body = serde_json::to_value(req)?;
40 self.http
41 .request_json(
42 Method::POST,
43 &format!("/question/{}/reply", request_id),
44 Some(body),
45 )
46 .await
47 }
48
49 pub async fn reject(&self, request_id: &str) -> Result<bool> {
57 self.http
58 .request_json(
59 Method::POST,
60 &format!("/question/{}/reject", request_id),
61 None,
62 )
63 .await
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::http::HttpConfig;
71 use std::time::Duration;
72 use wiremock::matchers::{body_json, method, path};
73 use wiremock::{Mock, MockServer, ResponseTemplate};
74
75 #[tokio::test]
76 async fn test_list_questions() {
77 let mock_server = MockServer::start().await;
78
79 Mock::given(method("GET"))
80 .and(path("/question"))
81 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
82 {
83 "id": "q-1",
84 "sessionId": "s-1",
85 "questions": [
86 {
87 "question": "Select one",
88 "header": "Decision",
89 "options": [
90 {"label": "yes", "description": "Allow"},
91 {"label": "no", "description": "Reject"}
92 ]
93 }
94 ]
95 }
96 ])))
97 .mount(&mock_server)
98 .await;
99
100 let http = HttpClient::new(HttpConfig {
101 base_url: mock_server.uri(),
102 directory: None,
103 timeout: Duration::from_secs(30),
104 })
105 .unwrap();
106
107 let questions = QuestionsApi::new(http);
108 let list = questions.list().await.unwrap();
109 assert_eq!(list.len(), 1);
110 assert_eq!(list[0].id, "q-1");
111 assert_eq!(list[0].questions[0].header, "Decision");
112 }
113
114 #[tokio::test]
115 async fn test_reply_question() {
116 let mock_server = MockServer::start().await;
117
118 Mock::given(method("POST"))
119 .and(path("/question/q-1/reply"))
120 .and(body_json(serde_json::json!({
121 "answers": [["yes"]]
122 })))
123 .respond_with(ResponseTemplate::new(200).set_body_json(true))
124 .mount(&mock_server)
125 .await;
126
127 let http = HttpClient::new(HttpConfig {
128 base_url: mock_server.uri(),
129 directory: None,
130 timeout: Duration::from_secs(30),
131 })
132 .unwrap();
133
134 let questions = QuestionsApi::new(http);
135 let accepted = questions
136 .reply(
137 "q-1",
138 &QuestionReplyRequest {
139 answers: vec![vec!["yes".to_string()]],
140 },
141 )
142 .await
143 .unwrap();
144 assert!(accepted);
145 }
146
147 #[tokio::test]
148 async fn test_reject_question() {
149 let mock_server = MockServer::start().await;
150
151 Mock::given(method("POST"))
152 .and(path("/question/q-2/reject"))
153 .respond_with(ResponseTemplate::new(200).set_body_json(true))
154 .mount(&mock_server)
155 .await;
156
157 let http = HttpClient::new(HttpConfig {
158 base_url: mock_server.uri(),
159 directory: None,
160 timeout: Duration::from_secs(30),
161 })
162 .unwrap();
163
164 let questions = QuestionsApi::new(http);
165 let rejected = questions.reject("q-2").await.unwrap();
166 assert!(rejected);
167 }
168
169 #[tokio::test]
170 async fn test_question_reply_not_found() {
171 let mock_server = MockServer::start().await;
172
173 Mock::given(method("POST"))
174 .and(path("/question/missing/reply"))
175 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
176 "name": "NotFound",
177 "message": "Question request not found"
178 })))
179 .mount(&mock_server)
180 .await;
181
182 let http = HttpClient::new(HttpConfig {
183 base_url: mock_server.uri(),
184 directory: None,
185 timeout: Duration::from_secs(30),
186 })
187 .unwrap();
188
189 let questions = QuestionsApi::new(http);
190 let result = questions
191 .reply(
192 "missing",
193 &QuestionReplyRequest {
194 answers: vec![vec!["yes".to_string()]],
195 },
196 )
197 .await;
198 assert!(result.is_err());
199 assert!(result.unwrap_err().is_not_found());
200 }
201}