@moejay/wrightty 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +14 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/terminal.d.ts +48 -0
- package/dist/terminal.js +210 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +3 -0
- package/package.json +35 -15
- package/.github/workflows/ci.yml +0 -90
- package/.github/workflows/release.yml +0 -177
- package/Cargo.lock +0 -2662
- package/Cargo.toml +0 -38
- package/PROTOCOL.md +0 -1351
- package/README.md +0 -386
- package/agents/ceo/AGENTS.md +0 -24
- package/agents/ceo/HEARTBEAT.md +0 -72
- package/agents/ceo/SOUL.md +0 -33
- package/agents/ceo/TOOLS.md +0 -3
- package/agents/founding-engineer/AGENTS.md +0 -44
- package/crates/wrightty/Cargo.toml +0 -43
- package/crates/wrightty/src/client_cmds.rs +0 -366
- package/crates/wrightty/src/discover.rs +0 -78
- package/crates/wrightty/src/main.rs +0 -100
- package/crates/wrightty/src/server.rs +0 -100
- package/crates/wrightty/src/term.rs +0 -338
- package/crates/wrightty-bridge-ghostty/Cargo.toml +0 -27
- package/crates/wrightty-bridge-ghostty/src/ghostty.rs +0 -422
- package/crates/wrightty-bridge-ghostty/src/lib.rs +0 -2
- package/crates/wrightty-bridge-ghostty/src/main.rs +0 -146
- package/crates/wrightty-bridge-ghostty/src/rpc.rs +0 -307
- package/crates/wrightty-bridge-kitty/Cargo.toml +0 -26
- package/crates/wrightty-bridge-kitty/src/kitty.rs +0 -269
- package/crates/wrightty-bridge-kitty/src/lib.rs +0 -2
- package/crates/wrightty-bridge-kitty/src/main.rs +0 -124
- package/crates/wrightty-bridge-kitty/src/rpc.rs +0 -304
- package/crates/wrightty-bridge-tmux/Cargo.toml +0 -26
- package/crates/wrightty-bridge-tmux/src/lib.rs +0 -2
- package/crates/wrightty-bridge-tmux/src/main.rs +0 -119
- package/crates/wrightty-bridge-tmux/src/rpc.rs +0 -291
- package/crates/wrightty-bridge-tmux/src/tmux.rs +0 -215
- package/crates/wrightty-bridge-wezterm/Cargo.toml +0 -26
- package/crates/wrightty-bridge-wezterm/src/lib.rs +0 -2
- package/crates/wrightty-bridge-wezterm/src/main.rs +0 -119
- package/crates/wrightty-bridge-wezterm/src/rpc.rs +0 -339
- package/crates/wrightty-bridge-wezterm/src/wezterm.rs +0 -190
- package/crates/wrightty-bridge-zellij/Cargo.toml +0 -27
- package/crates/wrightty-bridge-zellij/src/lib.rs +0 -2
- package/crates/wrightty-bridge-zellij/src/main.rs +0 -125
- package/crates/wrightty-bridge-zellij/src/rpc.rs +0 -328
- package/crates/wrightty-bridge-zellij/src/zellij.rs +0 -199
- package/crates/wrightty-client/Cargo.toml +0 -16
- package/crates/wrightty-client/src/client.rs +0 -254
- package/crates/wrightty-client/src/lib.rs +0 -2
- package/crates/wrightty-core/Cargo.toml +0 -21
- package/crates/wrightty-core/src/input.rs +0 -212
- package/crates/wrightty-core/src/lib.rs +0 -4
- package/crates/wrightty-core/src/screen.rs +0 -325
- package/crates/wrightty-core/src/session.rs +0 -249
- package/crates/wrightty-core/src/session_manager.rs +0 -77
- package/crates/wrightty-protocol/Cargo.toml +0 -13
- package/crates/wrightty-protocol/src/error.rs +0 -8
- package/crates/wrightty-protocol/src/events.rs +0 -138
- package/crates/wrightty-protocol/src/lib.rs +0 -4
- package/crates/wrightty-protocol/src/methods.rs +0 -321
- package/crates/wrightty-protocol/src/types.rs +0 -201
- package/crates/wrightty-server/Cargo.toml +0 -23
- package/crates/wrightty-server/src/lib.rs +0 -2
- package/crates/wrightty-server/src/main.rs +0 -65
- package/crates/wrightty-server/src/rpc.rs +0 -455
- package/crates/wrightty-server/src/state.rs +0 -39
- package/examples/basic_command.py +0 -53
- package/examples/interactive_tui.py +0 -86
- package/examples/record_session.py +0 -96
- package/install.sh +0 -81
- package/sdks/node/package-lock.json +0 -85
- package/sdks/node/package.json +0 -44
- package/sdks/node/src/client.ts +0 -94
- package/sdks/node/src/index.ts +0 -19
- package/sdks/node/src/terminal.ts +0 -258
- package/sdks/node/src/types.ts +0 -105
- package/sdks/node/tsconfig.json +0 -17
- package/sdks/python/README.md +0 -96
- package/sdks/python/pyproject.toml +0 -42
- package/sdks/python/wrightty/__init__.py +0 -6
- package/sdks/python/wrightty/cli.py +0 -210
- package/sdks/python/wrightty/client.py +0 -136
- package/sdks/python/wrightty/mcp_server.py +0 -434
- package/sdks/python/wrightty/terminal.py +0 -333
- package/skills/wrightty/SKILL.md +0 -261
- package/src/lib.rs +0 -1
- package/tests/integration_test.rs +0 -618
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
2
|
-
use std::time::Duration;
|
|
3
|
-
|
|
4
|
-
use wrightty_client::WrighttyClient;
|
|
5
|
-
use wrightty_protocol::methods::SessionCreateParams;
|
|
6
|
-
use wrightty_protocol::types::{KeyInput, ScreenshotFormat};
|
|
7
|
-
use wrightty_server::rpc::build_rpc_module;
|
|
8
|
-
use wrightty_server::state::AppState;
|
|
9
|
-
|
|
10
|
-
/// Start the server on a random available port and return the URL.
|
|
11
|
-
async fn start_server() -> (String, jsonrpsee::server::ServerHandle) {
|
|
12
|
-
let state = AppState::new(64);
|
|
13
|
-
let module = build_rpc_module(state).unwrap();
|
|
14
|
-
|
|
15
|
-
let server = jsonrpsee::server::Server::builder()
|
|
16
|
-
.build("127.0.0.1:0")
|
|
17
|
-
.await
|
|
18
|
-
.unwrap();
|
|
19
|
-
|
|
20
|
-
let addr = server.local_addr().unwrap();
|
|
21
|
-
let handle = server.start(module);
|
|
22
|
-
|
|
23
|
-
(format!("ws://{addr}"), handle)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
fn default_session() -> SessionCreateParams {
|
|
27
|
-
SessionCreateParams {
|
|
28
|
-
shell: Some("/bin/sh".to_string()),
|
|
29
|
-
args: vec![],
|
|
30
|
-
cols: 80,
|
|
31
|
-
rows: 24,
|
|
32
|
-
env: HashMap::new(),
|
|
33
|
-
cwd: None,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ─── Existing tests (kept) ───────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
#[tokio::test]
|
|
40
|
-
async fn test_create_session_and_read_screen() {
|
|
41
|
-
let (url, _handle) = start_server().await;
|
|
42
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
43
|
-
|
|
44
|
-
// Create a session
|
|
45
|
-
let session_id = client
|
|
46
|
-
.session_create(SessionCreateParams {
|
|
47
|
-
shell: Some("/bin/sh".to_string()),
|
|
48
|
-
args: vec![],
|
|
49
|
-
cols: 80,
|
|
50
|
-
rows: 24,
|
|
51
|
-
env: HashMap::new(),
|
|
52
|
-
cwd: None,
|
|
53
|
-
})
|
|
54
|
-
.await
|
|
55
|
-
.unwrap();
|
|
56
|
-
|
|
57
|
-
// Give the shell a moment to start and print its prompt
|
|
58
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
59
|
-
|
|
60
|
-
// Send "echo hello" + Enter
|
|
61
|
-
client
|
|
62
|
-
.send_text(&session_id, "echo hello\n")
|
|
63
|
-
.await
|
|
64
|
-
.unwrap();
|
|
65
|
-
|
|
66
|
-
// Wait for output to be processed
|
|
67
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
68
|
-
|
|
69
|
-
// Read the screen
|
|
70
|
-
let text = client.get_text(&session_id).await.unwrap();
|
|
71
|
-
println!("Screen text:\n---\n{text}\n---");
|
|
72
|
-
|
|
73
|
-
assert!(
|
|
74
|
-
text.contains("hello"),
|
|
75
|
-
"Expected 'hello' in screen output, got:\n{text}"
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Clean up
|
|
79
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#[tokio::test]
|
|
83
|
-
async fn test_send_keys() {
|
|
84
|
-
let (url, _handle) = start_server().await;
|
|
85
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
86
|
-
|
|
87
|
-
let session_id = client
|
|
88
|
-
.session_create(SessionCreateParams {
|
|
89
|
-
shell: Some("/bin/sh".to_string()),
|
|
90
|
-
args: vec![],
|
|
91
|
-
cols: 80,
|
|
92
|
-
rows: 24,
|
|
93
|
-
env: HashMap::new(),
|
|
94
|
-
cwd: None,
|
|
95
|
-
})
|
|
96
|
-
.await
|
|
97
|
-
.unwrap();
|
|
98
|
-
|
|
99
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
100
|
-
|
|
101
|
-
// Send keystrokes using the shorthand format
|
|
102
|
-
let keys: Vec<KeyInput> = vec![
|
|
103
|
-
KeyInput::Shorthand("e".to_string()),
|
|
104
|
-
KeyInput::Shorthand("c".to_string()),
|
|
105
|
-
KeyInput::Shorthand("h".to_string()),
|
|
106
|
-
KeyInput::Shorthand("o".to_string()),
|
|
107
|
-
KeyInput::Shorthand(" ".to_string()),
|
|
108
|
-
KeyInput::Shorthand("w".to_string()),
|
|
109
|
-
KeyInput::Shorthand("o".to_string()),
|
|
110
|
-
KeyInput::Shorthand("r".to_string()),
|
|
111
|
-
KeyInput::Shorthand("l".to_string()),
|
|
112
|
-
KeyInput::Shorthand("d".to_string()),
|
|
113
|
-
KeyInput::Shorthand("Enter".to_string()),
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
client.send_keys(&session_id, keys).await.unwrap();
|
|
117
|
-
|
|
118
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
119
|
-
|
|
120
|
-
let text = client.get_text(&session_id).await.unwrap();
|
|
121
|
-
println!("Screen text:\n---\n{text}\n---");
|
|
122
|
-
|
|
123
|
-
assert!(
|
|
124
|
-
text.contains("world"),
|
|
125
|
-
"Expected 'world' in screen output, got:\n{text}"
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
#[tokio::test]
|
|
132
|
-
async fn test_resize() {
|
|
133
|
-
let (url, _handle) = start_server().await;
|
|
134
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
135
|
-
|
|
136
|
-
let session_id = client
|
|
137
|
-
.session_create(SessionCreateParams {
|
|
138
|
-
shell: Some("/bin/sh".to_string()),
|
|
139
|
-
args: vec![],
|
|
140
|
-
cols: 80,
|
|
141
|
-
rows: 24,
|
|
142
|
-
env: HashMap::new(),
|
|
143
|
-
cwd: None,
|
|
144
|
-
})
|
|
145
|
-
.await
|
|
146
|
-
.unwrap();
|
|
147
|
-
|
|
148
|
-
// Check initial size
|
|
149
|
-
let (cols, rows) = client.get_size(&session_id).await.unwrap();
|
|
150
|
-
assert_eq!(cols, 80);
|
|
151
|
-
assert_eq!(rows, 24);
|
|
152
|
-
|
|
153
|
-
// Resize
|
|
154
|
-
client.resize(&session_id, 120, 40).await.unwrap();
|
|
155
|
-
|
|
156
|
-
// Verify new size
|
|
157
|
-
let (cols, rows) = client.get_size(&session_id).await.unwrap();
|
|
158
|
-
assert_eq!(cols, 120);
|
|
159
|
-
assert_eq!(rows, 40);
|
|
160
|
-
|
|
161
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
#[tokio::test]
|
|
165
|
-
async fn test_session_list() {
|
|
166
|
-
let (url, _handle) = start_server().await;
|
|
167
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
168
|
-
|
|
169
|
-
// No sessions initially
|
|
170
|
-
let sessions = client.session_list().await.unwrap();
|
|
171
|
-
assert!(sessions.is_empty());
|
|
172
|
-
|
|
173
|
-
// Create two sessions
|
|
174
|
-
let id1 = client
|
|
175
|
-
.session_create(SessionCreateParams {
|
|
176
|
-
shell: Some("/bin/sh".to_string()),
|
|
177
|
-
args: vec![],
|
|
178
|
-
cols: 80,
|
|
179
|
-
rows: 24,
|
|
180
|
-
env: HashMap::new(),
|
|
181
|
-
cwd: None,
|
|
182
|
-
})
|
|
183
|
-
.await
|
|
184
|
-
.unwrap();
|
|
185
|
-
|
|
186
|
-
let id2 = client
|
|
187
|
-
.session_create(SessionCreateParams {
|
|
188
|
-
shell: Some("/bin/sh".to_string()),
|
|
189
|
-
args: vec![],
|
|
190
|
-
cols: 80,
|
|
191
|
-
rows: 24,
|
|
192
|
-
env: HashMap::new(),
|
|
193
|
-
cwd: None,
|
|
194
|
-
})
|
|
195
|
-
.await
|
|
196
|
-
.unwrap();
|
|
197
|
-
|
|
198
|
-
let sessions = client.session_list().await.unwrap();
|
|
199
|
-
assert_eq!(sessions.len(), 2);
|
|
200
|
-
|
|
201
|
-
// Destroy one
|
|
202
|
-
client.session_destroy(&id1).await.unwrap();
|
|
203
|
-
|
|
204
|
-
let sessions = client.session_list().await.unwrap();
|
|
205
|
-
assert_eq!(sessions.len(), 1);
|
|
206
|
-
assert_eq!(sessions[0].session_id, id2);
|
|
207
|
-
|
|
208
|
-
client.session_destroy(&id2).await.unwrap();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
#[tokio::test]
|
|
212
|
-
async fn test_get_info() {
|
|
213
|
-
let (url, _handle) = start_server().await;
|
|
214
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
215
|
-
|
|
216
|
-
let info = client.get_info().await.unwrap();
|
|
217
|
-
assert_eq!(info.version, "0.1.0");
|
|
218
|
-
assert_eq!(info.implementation, "wrightty-server");
|
|
219
|
-
assert!(info.capabilities.supports_resize);
|
|
220
|
-
assert!(info.capabilities.supports_session_create);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ─── Screen domain ───────────────────────────────────────────────────────────
|
|
224
|
-
|
|
225
|
-
#[tokio::test]
|
|
226
|
-
async fn test_screen_get_contents_dimensions() {
|
|
227
|
-
let (url, _handle) = start_server().await;
|
|
228
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
229
|
-
|
|
230
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
231
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
232
|
-
|
|
233
|
-
let contents = client.get_contents(&session_id).await.unwrap();
|
|
234
|
-
|
|
235
|
-
// Screen dimensions match what we requested
|
|
236
|
-
assert_eq!(contents.cols, 80);
|
|
237
|
-
assert_eq!(contents.rows, 24);
|
|
238
|
-
|
|
239
|
-
// Cell grid has the right shape
|
|
240
|
-
assert_eq!(contents.cells.len(), 24, "expected 24 rows");
|
|
241
|
-
for row in &contents.cells {
|
|
242
|
-
assert_eq!(row.len(), 80, "expected 80 cols per row");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Cursor is within bounds
|
|
246
|
-
assert!(contents.cursor.row < 24);
|
|
247
|
-
assert!(contents.cursor.col < 80);
|
|
248
|
-
|
|
249
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
#[tokio::test]
|
|
253
|
-
async fn test_screen_get_contents_cell_data() {
|
|
254
|
-
let (url, _handle) = start_server().await;
|
|
255
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
256
|
-
|
|
257
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
258
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
259
|
-
|
|
260
|
-
client.send_text(&session_id, "echo hi\n").await.unwrap();
|
|
261
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
262
|
-
|
|
263
|
-
let contents = client.get_contents(&session_id).await.unwrap();
|
|
264
|
-
|
|
265
|
-
// Flatten all chars and find "h","i" somewhere
|
|
266
|
-
let all_chars: String = contents.cells.iter().flat_map(|row| row.iter().map(|c| c.char.as_str())).collect();
|
|
267
|
-
assert!(all_chars.contains('h'), "expected 'h' in cell data");
|
|
268
|
-
assert!(all_chars.contains('i'), "expected 'i' in cell data");
|
|
269
|
-
|
|
270
|
-
// Each cell has valid width (0, 1, or 2)
|
|
271
|
-
for row in &contents.cells {
|
|
272
|
-
for cell in row {
|
|
273
|
-
assert!(cell.width <= 2, "unexpected cell width: {}", cell.width);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
#[tokio::test]
|
|
281
|
-
async fn test_screen_get_scrollback_empty_initially() {
|
|
282
|
-
let (url, _handle) = start_server().await;
|
|
283
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
284
|
-
|
|
285
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
286
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
287
|
-
|
|
288
|
-
// Fresh terminal has no scrollback
|
|
289
|
-
let result = client.get_scrollback(&session_id, 100, 0).await.unwrap();
|
|
290
|
-
assert_eq!(result.total_scrollback, 0);
|
|
291
|
-
assert!(result.lines.is_empty());
|
|
292
|
-
|
|
293
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
#[tokio::test]
|
|
297
|
-
async fn test_screen_get_scrollback_after_fill() {
|
|
298
|
-
let (url, _handle) = start_server().await;
|
|
299
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
300
|
-
|
|
301
|
-
// Small terminal so it scrolls quickly
|
|
302
|
-
let session_id = client
|
|
303
|
-
.session_create(SessionCreateParams {
|
|
304
|
-
shell: Some("/bin/sh".to_string()),
|
|
305
|
-
args: vec![],
|
|
306
|
-
cols: 80,
|
|
307
|
-
rows: 5,
|
|
308
|
-
env: HashMap::new(),
|
|
309
|
-
cwd: None,
|
|
310
|
-
})
|
|
311
|
-
.await
|
|
312
|
-
.unwrap();
|
|
313
|
-
|
|
314
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
315
|
-
|
|
316
|
-
// Print more lines than the terminal height so scrollback fills up
|
|
317
|
-
for i in 0..10 {
|
|
318
|
-
client
|
|
319
|
-
.send_text(&session_id, &format!("echo line{i}\n"))
|
|
320
|
-
.await
|
|
321
|
-
.unwrap();
|
|
322
|
-
tokio::time::sleep(Duration::from_millis(150)).await;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
let result = client.get_scrollback(&session_id, 100, 0).await.unwrap();
|
|
326
|
-
println!("Scrollback lines: {}", result.total_scrollback);
|
|
327
|
-
println!("Returned: {:?}", result.lines.iter().map(|l| &l.text).collect::<Vec<_>>());
|
|
328
|
-
|
|
329
|
-
// We should have some scrollback now
|
|
330
|
-
assert!(result.total_scrollback > 0, "expected scrollback after filling terminal");
|
|
331
|
-
// Line numbers in scrollback are negative
|
|
332
|
-
for line in &result.lines {
|
|
333
|
-
assert!(line.line_number < 0, "scrollback line_number should be negative, got {}", line.line_number);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
#[tokio::test]
|
|
340
|
-
async fn test_screen_screenshot_text_format() {
|
|
341
|
-
let (url, _handle) = start_server().await;
|
|
342
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
343
|
-
|
|
344
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
345
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
346
|
-
|
|
347
|
-
client.send_text(&session_id, "echo screenshot_test\n").await.unwrap();
|
|
348
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
349
|
-
|
|
350
|
-
let result = client.screenshot(&session_id, ScreenshotFormat::Text).await.unwrap();
|
|
351
|
-
|
|
352
|
-
assert!(
|
|
353
|
-
matches!(result.format, ScreenshotFormat::Text),
|
|
354
|
-
"format should be Text"
|
|
355
|
-
);
|
|
356
|
-
assert!(
|
|
357
|
-
result.data.contains("screenshot_test"),
|
|
358
|
-
"screenshot data should contain the echoed text, got: {}",
|
|
359
|
-
result.data
|
|
360
|
-
);
|
|
361
|
-
// Text format has no pixel dimensions
|
|
362
|
-
assert!(result.width.is_none());
|
|
363
|
-
assert!(result.height.is_none());
|
|
364
|
-
|
|
365
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
#[tokio::test]
|
|
369
|
-
async fn test_screen_screenshot_json_format() {
|
|
370
|
-
let (url, _handle) = start_server().await;
|
|
371
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
372
|
-
|
|
373
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
374
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
375
|
-
|
|
376
|
-
let result = client.screenshot(&session_id, ScreenshotFormat::Json).await.unwrap();
|
|
377
|
-
|
|
378
|
-
assert!(matches!(result.format, ScreenshotFormat::Json));
|
|
379
|
-
assert_eq!(result.width, Some(80));
|
|
380
|
-
assert_eq!(result.height, Some(24));
|
|
381
|
-
// data is valid JSON
|
|
382
|
-
let parsed: serde_json::Value = serde_json::from_str(&result.data).expect("JSON screenshot data should be valid JSON");
|
|
383
|
-
assert!(parsed.is_array(), "JSON screenshot should be an array of rows");
|
|
384
|
-
|
|
385
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
#[tokio::test]
|
|
389
|
-
async fn test_screen_wait_for_text_success() {
|
|
390
|
-
let (url, _handle) = start_server().await;
|
|
391
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
392
|
-
|
|
393
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
394
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
395
|
-
|
|
396
|
-
// Send text in the background, then wait for it
|
|
397
|
-
client.send_text(&session_id, "echo wait_target\n").await.unwrap();
|
|
398
|
-
|
|
399
|
-
let result = client
|
|
400
|
-
.wait_for_text(&session_id, "wait_target", false, 5000)
|
|
401
|
-
.await
|
|
402
|
-
.unwrap();
|
|
403
|
-
|
|
404
|
-
assert!(result.found, "wait_for_text should have found 'wait_target'");
|
|
405
|
-
assert!(!result.matches.is_empty());
|
|
406
|
-
assert!(result.elapsed < 5000);
|
|
407
|
-
|
|
408
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
#[tokio::test]
|
|
412
|
-
async fn test_screen_wait_for_text_timeout() {
|
|
413
|
-
let (url, _handle) = start_server().await;
|
|
414
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
415
|
-
|
|
416
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
417
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
418
|
-
|
|
419
|
-
// Wait for text that will never appear
|
|
420
|
-
let result = client
|
|
421
|
-
.wait_for_text(&session_id, "this_text_will_never_appear_xyz", false, 300)
|
|
422
|
-
.await
|
|
423
|
-
.unwrap();
|
|
424
|
-
|
|
425
|
-
assert!(!result.found, "wait_for_text should have timed out");
|
|
426
|
-
assert!(result.matches.is_empty());
|
|
427
|
-
|
|
428
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
#[tokio::test]
|
|
432
|
-
async fn test_screen_wait_for_text_regex() {
|
|
433
|
-
let (url, _handle) = start_server().await;
|
|
434
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
435
|
-
|
|
436
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
437
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
438
|
-
|
|
439
|
-
client.send_text(&session_id, "echo regex123\n").await.unwrap();
|
|
440
|
-
|
|
441
|
-
// Regex: match "regex" followed by digits
|
|
442
|
-
let result = client
|
|
443
|
-
.wait_for_text(&session_id, r"regex\d+", true, 5000)
|
|
444
|
-
.await
|
|
445
|
-
.unwrap();
|
|
446
|
-
|
|
447
|
-
assert!(result.found, "regex wait should have matched");
|
|
448
|
-
assert!(!result.matches.is_empty());
|
|
449
|
-
assert!(result.matches[0].text.starts_with("regex"));
|
|
450
|
-
|
|
451
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// ─── Input domain ────────────────────────────────────────────────────────────
|
|
455
|
-
|
|
456
|
-
#[tokio::test]
|
|
457
|
-
async fn test_input_send_text_multiple_commands() {
|
|
458
|
-
let (url, _handle) = start_server().await;
|
|
459
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
460
|
-
|
|
461
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
462
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
463
|
-
|
|
464
|
-
// Send several commands
|
|
465
|
-
client.send_text(&session_id, "echo first\n").await.unwrap();
|
|
466
|
-
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
467
|
-
client.send_text(&session_id, "echo second\n").await.unwrap();
|
|
468
|
-
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
469
|
-
client.send_text(&session_id, "echo third\n").await.unwrap();
|
|
470
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
471
|
-
|
|
472
|
-
let text = client.get_text(&session_id).await.unwrap();
|
|
473
|
-
println!("Screen:\n{text}");
|
|
474
|
-
|
|
475
|
-
assert!(text.contains("first") || text.contains("second") || text.contains("third"),
|
|
476
|
-
"At least one output line should be visible");
|
|
477
|
-
|
|
478
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
#[tokio::test]
|
|
482
|
-
async fn test_input_send_keys_special_keys() {
|
|
483
|
-
let (url, _handle) = start_server().await;
|
|
484
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
485
|
-
|
|
486
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
487
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
488
|
-
|
|
489
|
-
// Type a partial command then backspace to erase it, then type correct command
|
|
490
|
-
let keys: Vec<KeyInput> = vec![
|
|
491
|
-
KeyInput::Shorthand("e".to_string()),
|
|
492
|
-
KeyInput::Shorthand("c".to_string()),
|
|
493
|
-
KeyInput::Shorthand("x".to_string()), // typo
|
|
494
|
-
KeyInput::Shorthand("Backspace".to_string()),
|
|
495
|
-
KeyInput::Shorthand("h".to_string()),
|
|
496
|
-
KeyInput::Shorthand("o".to_string()),
|
|
497
|
-
KeyInput::Shorthand(" ".to_string()),
|
|
498
|
-
KeyInput::Shorthand("k".to_string()),
|
|
499
|
-
KeyInput::Shorthand("e".to_string()),
|
|
500
|
-
KeyInput::Shorthand("y".to_string()),
|
|
501
|
-
KeyInput::Shorthand("s".to_string()),
|
|
502
|
-
KeyInput::Shorthand("Enter".to_string()),
|
|
503
|
-
];
|
|
504
|
-
|
|
505
|
-
client.send_keys(&session_id, keys).await.unwrap();
|
|
506
|
-
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
507
|
-
|
|
508
|
-
let text = client.get_text(&session_id).await.unwrap();
|
|
509
|
-
println!("Screen:\n{text}");
|
|
510
|
-
|
|
511
|
-
assert!(text.contains("keys"), "Expected 'keys' in output, got:\n{text}");
|
|
512
|
-
|
|
513
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// ─── Error cases ─────────────────────────────────────────────────────────────
|
|
517
|
-
|
|
518
|
-
#[tokio::test]
|
|
519
|
-
async fn test_error_get_text_invalid_session() {
|
|
520
|
-
let (url, _handle) = start_server().await;
|
|
521
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
522
|
-
|
|
523
|
-
let result = client.get_text("nonexistent-session-id").await;
|
|
524
|
-
assert!(result.is_err(), "should return error for invalid session ID");
|
|
525
|
-
let err = result.unwrap_err().to_string();
|
|
526
|
-
println!("Error: {err}");
|
|
527
|
-
assert!(
|
|
528
|
-
err.contains("1001") || err.contains("session not found"),
|
|
529
|
-
"expected SESSION_NOT_FOUND error code 1001, got: {err}"
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
#[tokio::test]
|
|
534
|
-
async fn test_error_get_contents_invalid_session() {
|
|
535
|
-
let (url, _handle) = start_server().await;
|
|
536
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
537
|
-
|
|
538
|
-
let result = client.get_contents("nonexistent-session-id").await;
|
|
539
|
-
assert!(result.is_err());
|
|
540
|
-
let err = result.unwrap_err().to_string();
|
|
541
|
-
assert!(
|
|
542
|
-
err.contains("1001") || err.contains("session not found"),
|
|
543
|
-
"expected SESSION_NOT_FOUND, got: {err}"
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
#[tokio::test]
|
|
548
|
-
async fn test_error_get_scrollback_invalid_session() {
|
|
549
|
-
let (url, _handle) = start_server().await;
|
|
550
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
551
|
-
|
|
552
|
-
let result = client.get_scrollback("nonexistent-session-id", 10, 0).await;
|
|
553
|
-
assert!(result.is_err());
|
|
554
|
-
let err = result.unwrap_err().to_string();
|
|
555
|
-
assert!(
|
|
556
|
-
err.contains("1001") || err.contains("session not found"),
|
|
557
|
-
"expected SESSION_NOT_FOUND, got: {err}"
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
#[tokio::test]
|
|
562
|
-
async fn test_error_send_text_invalid_session() {
|
|
563
|
-
let (url, _handle) = start_server().await;
|
|
564
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
565
|
-
|
|
566
|
-
let result = client.send_text("nonexistent-session-id", "hello\n").await;
|
|
567
|
-
assert!(result.is_err());
|
|
568
|
-
let err = result.unwrap_err().to_string();
|
|
569
|
-
assert!(
|
|
570
|
-
err.contains("1001") || err.contains("session not found"),
|
|
571
|
-
"expected SESSION_NOT_FOUND, got: {err}"
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
#[tokio::test]
|
|
576
|
-
async fn test_error_send_keys_invalid_session() {
|
|
577
|
-
let (url, _handle) = start_server().await;
|
|
578
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
579
|
-
|
|
580
|
-
let result = client
|
|
581
|
-
.send_keys("nonexistent-session-id", vec![KeyInput::Shorthand("a".to_string())])
|
|
582
|
-
.await;
|
|
583
|
-
assert!(result.is_err());
|
|
584
|
-
let err = result.unwrap_err().to_string();
|
|
585
|
-
assert!(
|
|
586
|
-
err.contains("1001") || err.contains("session not found"),
|
|
587
|
-
"expected SESSION_NOT_FOUND, got: {err}"
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
#[tokio::test]
|
|
592
|
-
async fn test_error_destroy_nonexistent_session() {
|
|
593
|
-
let (url, _handle) = start_server().await;
|
|
594
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
595
|
-
|
|
596
|
-
let result = client.session_destroy("nonexistent-session-id").await;
|
|
597
|
-
assert!(result.is_err());
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
#[tokio::test]
|
|
601
|
-
async fn test_error_screenshot_unsupported_format() {
|
|
602
|
-
let (url, _handle) = start_server().await;
|
|
603
|
-
let client = WrighttyClient::connect(&url).await.unwrap();
|
|
604
|
-
|
|
605
|
-
let session_id = client.session_create(default_session()).await.unwrap();
|
|
606
|
-
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
607
|
-
|
|
608
|
-
// PNG/SVG not implemented — should return NOT_SUPPORTED error
|
|
609
|
-
let result = client.screenshot(&session_id, ScreenshotFormat::Png).await;
|
|
610
|
-
assert!(result.is_err(), "PNG screenshot should return an error");
|
|
611
|
-
let err = result.unwrap_err().to_string();
|
|
612
|
-
assert!(
|
|
613
|
-
err.contains("1006") || err.contains("not supported"),
|
|
614
|
-
"expected NOT_SUPPORTED error code 1006, got: {err}"
|
|
615
|
-
);
|
|
616
|
-
|
|
617
|
-
client.session_destroy(&session_id).await.unwrap();
|
|
618
|
-
}
|