tokra 0.0.1.pre.1 → 0.0.1.pre.2

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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSES/MIT-0.txt +16 -0
  3. data/REUSE.toml +6 -1
  4. data/Rakefile +2 -0
  5. data/Steepfile +2 -0
  6. data/clippy_exceptions.rb +75 -37
  7. data/doc/contributors/adr/004.md +63 -0
  8. data/doc/contributors/chats/002.md +177 -0
  9. data/doc/contributors/chats/003.md +2180 -0
  10. data/doc/contributors/chats/004.md +1992 -0
  11. data/doc/contributors/chats/005.md +1529 -0
  12. data/doc/contributors/plan/002.md +173 -0
  13. data/doc/contributors/plan/003.md +111 -0
  14. data/examples/verify_hello_world/index.html +15 -2
  15. data/examples/verify_ping_pong/app.rb +3 -1
  16. data/examples/verify_ping_pong/public/styles.css +36 -9
  17. data/examples/verify_ping_pong/views/layout.erb +1 -1
  18. data/examples/verify_rails_sqlite/.dockerignore +51 -0
  19. data/examples/verify_rails_sqlite/.gitattributes +9 -0
  20. data/examples/verify_rails_sqlite/.github/dependabot.yml +12 -0
  21. data/examples/verify_rails_sqlite/.github/workflows/ci.yml +124 -0
  22. data/examples/verify_rails_sqlite/.gitignore +35 -0
  23. data/examples/verify_rails_sqlite/.kamal/hooks/docker-setup.sample +3 -0
  24. data/examples/verify_rails_sqlite/.kamal/hooks/post-app-boot.sample +3 -0
  25. data/examples/verify_rails_sqlite/.kamal/hooks/post-deploy.sample +14 -0
  26. data/examples/verify_rails_sqlite/.kamal/hooks/post-proxy-reboot.sample +3 -0
  27. data/examples/verify_rails_sqlite/.kamal/hooks/pre-app-boot.sample +3 -0
  28. data/examples/verify_rails_sqlite/.kamal/hooks/pre-build.sample +51 -0
  29. data/examples/verify_rails_sqlite/.kamal/hooks/pre-connect.sample +47 -0
  30. data/examples/verify_rails_sqlite/.kamal/hooks/pre-deploy.sample +122 -0
  31. data/examples/verify_rails_sqlite/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  32. data/examples/verify_rails_sqlite/.kamal/secrets +20 -0
  33. data/examples/verify_rails_sqlite/.rubocop.yml +2 -0
  34. data/examples/verify_rails_sqlite/.ruby-version +1 -0
  35. data/examples/verify_rails_sqlite/Dockerfile +77 -0
  36. data/examples/verify_rails_sqlite/Gemfile +66 -0
  37. data/examples/verify_rails_sqlite/Gemfile.lock +563 -0
  38. data/examples/verify_rails_sqlite/README.md +41 -0
  39. data/examples/verify_rails_sqlite/Rakefile +6 -0
  40. data/examples/verify_rails_sqlite/app/assets/images/.keep +0 -0
  41. data/examples/verify_rails_sqlite/app/assets/stylesheets/application.css +469 -0
  42. data/examples/verify_rails_sqlite/app/controllers/application_controller.rb +12 -0
  43. data/examples/verify_rails_sqlite/app/controllers/concerns/.keep +0 -0
  44. data/examples/verify_rails_sqlite/app/controllers/todos_controller.rb +70 -0
  45. data/examples/verify_rails_sqlite/app/helpers/application_helper.rb +2 -0
  46. data/examples/verify_rails_sqlite/app/helpers/todos_helper.rb +2 -0
  47. data/examples/verify_rails_sqlite/app/javascript/application.js +3 -0
  48. data/examples/verify_rails_sqlite/app/javascript/controllers/application.js +9 -0
  49. data/examples/verify_rails_sqlite/app/javascript/controllers/hello_controller.js +7 -0
  50. data/examples/verify_rails_sqlite/app/javascript/controllers/index.js +4 -0
  51. data/examples/verify_rails_sqlite/app/jobs/application_job.rb +7 -0
  52. data/examples/verify_rails_sqlite/app/mailers/application_mailer.rb +4 -0
  53. data/examples/verify_rails_sqlite/app/models/application_record.rb +3 -0
  54. data/examples/verify_rails_sqlite/app/models/concerns/.keep +0 -0
  55. data/examples/verify_rails_sqlite/app/models/todo.rb +10 -0
  56. data/examples/verify_rails_sqlite/app/views/layouts/application.html.erb +24 -0
  57. data/examples/verify_rails_sqlite/app/views/layouts/mailer.html.erb +13 -0
  58. data/examples/verify_rails_sqlite/app/views/layouts/mailer.text.erb +1 -0
  59. data/examples/verify_rails_sqlite/app/views/pwa/manifest.json.erb +22 -0
  60. data/examples/verify_rails_sqlite/app/views/pwa/service-worker.js +26 -0
  61. data/examples/verify_rails_sqlite/app/views/todos/_form.html.erb +27 -0
  62. data/examples/verify_rails_sqlite/app/views/todos/_todo.html.erb +18 -0
  63. data/examples/verify_rails_sqlite/app/views/todos/_todo.json.jbuilder +2 -0
  64. data/examples/verify_rails_sqlite/app/views/todos/edit.html.erb +7 -0
  65. data/examples/verify_rails_sqlite/app/views/todos/index.html.erb +22 -0
  66. data/examples/verify_rails_sqlite/app/views/todos/index.json.jbuilder +1 -0
  67. data/examples/verify_rails_sqlite/app/views/todos/new.html.erb +7 -0
  68. data/examples/verify_rails_sqlite/app/views/todos/show.html.erb +23 -0
  69. data/examples/verify_rails_sqlite/app/views/todos/show.json.jbuilder +1 -0
  70. data/examples/verify_rails_sqlite/bin/brakeman +7 -0
  71. data/examples/verify_rails_sqlite/bin/bundler-audit +6 -0
  72. data/examples/verify_rails_sqlite/bin/ci +6 -0
  73. data/examples/verify_rails_sqlite/bin/dev +2 -0
  74. data/examples/verify_rails_sqlite/bin/docker-entrypoint +8 -0
  75. data/examples/verify_rails_sqlite/bin/importmap +4 -0
  76. data/examples/verify_rails_sqlite/bin/jobs +6 -0
  77. data/examples/verify_rails_sqlite/bin/kamal +16 -0
  78. data/examples/verify_rails_sqlite/bin/rails +4 -0
  79. data/examples/verify_rails_sqlite/bin/rake +4 -0
  80. data/examples/verify_rails_sqlite/bin/rubocop +8 -0
  81. data/examples/verify_rails_sqlite/bin/setup +35 -0
  82. data/examples/verify_rails_sqlite/bin/thrust +5 -0
  83. data/examples/verify_rails_sqlite/config/application.rb +27 -0
  84. data/examples/verify_rails_sqlite/config/boot.rb +4 -0
  85. data/examples/verify_rails_sqlite/config/bundler-audit.yml +5 -0
  86. data/examples/verify_rails_sqlite/config/cable.yml +17 -0
  87. data/examples/verify_rails_sqlite/config/cache.yml +16 -0
  88. data/examples/verify_rails_sqlite/config/ci.rb +24 -0
  89. data/examples/verify_rails_sqlite/config/credentials.yml.enc +1 -0
  90. data/examples/verify_rails_sqlite/config/database.yml +40 -0
  91. data/examples/verify_rails_sqlite/config/deploy.yml +119 -0
  92. data/examples/verify_rails_sqlite/config/environment.rb +5 -0
  93. data/examples/verify_rails_sqlite/config/environments/development.rb +84 -0
  94. data/examples/verify_rails_sqlite/config/environments/production.rb +99 -0
  95. data/examples/verify_rails_sqlite/config/environments/test.rb +53 -0
  96. data/examples/verify_rails_sqlite/config/importmap.rb +7 -0
  97. data/examples/verify_rails_sqlite/config/initializers/assets.rb +7 -0
  98. data/examples/verify_rails_sqlite/config/initializers/content_security_policy.rb +29 -0
  99. data/examples/verify_rails_sqlite/config/initializers/filter_parameter_logging.rb +8 -0
  100. data/examples/verify_rails_sqlite/config/initializers/inflections.rb +16 -0
  101. data/examples/verify_rails_sqlite/config/locales/en.yml +31 -0
  102. data/examples/verify_rails_sqlite/config/puma.rb +42 -0
  103. data/examples/verify_rails_sqlite/config/queue.yml +18 -0
  104. data/examples/verify_rails_sqlite/config/recurring.yml +15 -0
  105. data/examples/verify_rails_sqlite/config/routes.rb +15 -0
  106. data/examples/verify_rails_sqlite/config/storage.yml +27 -0
  107. data/examples/verify_rails_sqlite/config.ru +6 -0
  108. data/examples/verify_rails_sqlite/db/cable_schema.rb +11 -0
  109. data/examples/verify_rails_sqlite/db/cache_schema.rb +12 -0
  110. data/examples/verify_rails_sqlite/db/migrate/20260130080933_create_todos.rb +10 -0
  111. data/examples/verify_rails_sqlite/db/queue_schema.rb +129 -0
  112. data/examples/verify_rails_sqlite/db/schema.rb +20 -0
  113. data/examples/verify_rails_sqlite/db/seeds.rb +9 -0
  114. data/examples/verify_rails_sqlite/lib/tasks/.keep +0 -0
  115. data/examples/verify_rails_sqlite/log/.keep +0 -0
  116. data/examples/verify_rails_sqlite/public/400.html +135 -0
  117. data/examples/verify_rails_sqlite/public/404.html +135 -0
  118. data/examples/verify_rails_sqlite/public/406-unsupported-browser.html +135 -0
  119. data/examples/verify_rails_sqlite/public/422.html +135 -0
  120. data/examples/verify_rails_sqlite/public/500.html +135 -0
  121. data/examples/verify_rails_sqlite/public/icon.png +0 -0
  122. data/examples/verify_rails_sqlite/public/icon.svg +3 -0
  123. data/examples/verify_rails_sqlite/public/robots.txt +1 -0
  124. data/examples/verify_rails_sqlite/public/styles.css +469 -0
  125. data/examples/verify_rails_sqlite/script/.keep +0 -0
  126. data/examples/verify_rails_sqlite/storage/.keep +0 -0
  127. data/examples/verify_rails_sqlite/test/controllers/.keep +0 -0
  128. data/examples/verify_rails_sqlite/test/controllers/todos_controller_test.rb +48 -0
  129. data/examples/verify_rails_sqlite/test/fixtures/files/.keep +0 -0
  130. data/examples/verify_rails_sqlite/test/fixtures/todos.yml +9 -0
  131. data/examples/verify_rails_sqlite/test/helpers/.keep +0 -0
  132. data/examples/verify_rails_sqlite/test/integration/.keep +0 -0
  133. data/examples/verify_rails_sqlite/test/mailers/.keep +0 -0
  134. data/examples/verify_rails_sqlite/test/models/.keep +0 -0
  135. data/examples/verify_rails_sqlite/test/models/todo_test.rb +7 -0
  136. data/examples/verify_rails_sqlite/test/test_helper.rb +15 -0
  137. data/examples/verify_rails_sqlite/tmp/.keep +0 -0
  138. data/examples/verify_rails_sqlite/tmp/pids/.keep +0 -0
  139. data/examples/verify_rails_sqlite/tmp/storage/.keep +0 -0
  140. data/examples/verify_rails_sqlite/tokra.rb +42 -0
  141. data/examples/verify_rails_sqlite/vendor/.keep +0 -0
  142. data/examples/verify_rails_sqlite/vendor/javascript/.keep +0 -0
  143. data/ext/tokra/src/event_loop.rs +206 -0
  144. data/ext/tokra/src/events.rs +430 -0
  145. data/ext/tokra/src/lib.rs +52 -664
  146. data/ext/tokra/src/proxy.rs +142 -0
  147. data/ext/tokra/src/responders.rs +86 -0
  148. data/ext/tokra/src/user_event.rs +313 -0
  149. data/ext/tokra/src/webview.rs +194 -0
  150. data/ext/tokra/src/window.rs +92 -0
  151. data/lib/tokra/rack/handler.rb +37 -14
  152. data/lib/tokra/version.rb +1 -1
  153. data/rbs_exceptions.rb +12 -0
  154. data/sig/tokra.rbs +95 -1
  155. data/tasks/lint.rake +2 -2
  156. data/tasks/lint.rb +49 -0
  157. data/tasks/rust.rake +6 -23
  158. data/tasks/steep.rake +25 -3
  159. data/tasks/test.rake +1 -1
  160. metadata +143 -1
@@ -0,0 +1,430 @@
1
+ // SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ //! Ruby event classes for the event loop callback.
5
+ //!
6
+ //! These are simple data classes that are created by the event loop and
7
+ //! passed to the Ruby callback. They carry event data from native code
8
+ //! to Ruby land.
9
+
10
+ use magnus::{function, method, prelude::*, Error, Ruby};
11
+
12
+ // =============================================================================
13
+ // IpcEvent
14
+ // =============================================================================
15
+
16
+ /// Simple Ruby class for IPC events.
17
+ #[magnus::wrap(class = "Tokra::Native::IpcEvent", free_immediately, size)]
18
+ pub struct RbIpcEvent {
19
+ message: String,
20
+ }
21
+
22
+ impl RbIpcEvent {
23
+ /// Create a new IPC event.
24
+ #[must_use]
25
+ pub const fn new(message: String) -> Self {
26
+ Self { message }
27
+ }
28
+
29
+ /// Get the message content.
30
+ #[must_use]
31
+ pub fn message(&self) -> String {
32
+ self.message.clone()
33
+ }
34
+ }
35
+
36
+ // =============================================================================
37
+ // WakeUpEvent
38
+ // =============================================================================
39
+
40
+ /// Simple Ruby class for wake-up events.
41
+ #[magnus::wrap(class = "Tokra::Native::WakeUpEvent", free_immediately, size)]
42
+ pub struct RbWakeUpEvent {
43
+ payload: String,
44
+ }
45
+
46
+ impl RbWakeUpEvent {
47
+ /// Create a new wake-up event.
48
+ #[must_use]
49
+ pub const fn new(payload: String) -> Self {
50
+ Self { payload }
51
+ }
52
+
53
+ /// Get the payload content.
54
+ #[must_use]
55
+ pub fn payload(&self) -> String {
56
+ self.payload.clone()
57
+ }
58
+ }
59
+
60
+ // =============================================================================
61
+ // WindowCloseEvent
62
+ // =============================================================================
63
+
64
+ /// Simple Ruby class for window close events.
65
+ #[magnus::wrap(class = "Tokra::Native::WindowCloseEvent", free_immediately, size)]
66
+ pub struct RbWindowCloseEvent;
67
+
68
+ impl RbWindowCloseEvent {
69
+ /// Create a new window close event.
70
+ #[must_use]
71
+ pub const fn new() -> Self {
72
+ Self
73
+ }
74
+ }
75
+
76
+ impl Default for RbWindowCloseEvent {
77
+ fn default() -> Self {
78
+ Self::new()
79
+ }
80
+ }
81
+
82
+ // =============================================================================
83
+ // HttpRequestEvent
84
+ // =============================================================================
85
+
86
+ /// Ruby class for HTTP request events (from `tokra://` custom protocol).
87
+ /// Includes `request_id` for correlating async responses.
88
+ #[magnus::wrap(class = "Tokra::Native::HttpRequestEvent", free_immediately, size)]
89
+ pub struct RbHttpRequestEvent {
90
+ request_id: u64,
91
+ method: String,
92
+ uri: String,
93
+ headers: Vec<(String, String)>,
94
+ body: String,
95
+ }
96
+
97
+ impl RbHttpRequestEvent {
98
+ /// Create a new HTTP request event.
99
+ #[must_use]
100
+ pub const fn new(
101
+ request_id: u64,
102
+ method: String,
103
+ uri: String,
104
+ headers: Vec<(String, String)>,
105
+ body: String,
106
+ ) -> Self {
107
+ Self {
108
+ request_id,
109
+ method,
110
+ uri,
111
+ headers,
112
+ body,
113
+ }
114
+ }
115
+
116
+ /// Get the request ID.
117
+ #[must_use]
118
+ pub const fn request_id(&self) -> u64 {
119
+ self.request_id
120
+ }
121
+
122
+ /// Get the HTTP method.
123
+ #[must_use]
124
+ pub fn method(&self) -> String {
125
+ self.method.clone()
126
+ }
127
+
128
+ /// Get the request URI.
129
+ #[must_use]
130
+ pub fn uri(&self) -> String {
131
+ self.uri.clone()
132
+ }
133
+
134
+ /// Get the request headers as array of [name, value] pairs.
135
+ /// Magnus automatically converts Vec<(String, String)> to Ruby Array of Arrays.
136
+ #[must_use]
137
+ pub fn headers(&self) -> Vec<(String, String)> {
138
+ self.headers.clone()
139
+ }
140
+
141
+ /// Get the request body.
142
+ #[must_use]
143
+ pub fn body(&self) -> String {
144
+ self.body.clone()
145
+ }
146
+ }
147
+
148
+ // =============================================================================
149
+ // PageLoadEvent
150
+ // =============================================================================
151
+
152
+ // SPDX-SnippetBegin
153
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
154
+ // SPDX-SnippetCopyrightText: 2020-2023 Tauri Programme within The Commons Conservancy
155
+ // SPDX-SnippetComment: PageLoadEvent pattern adapted from tauri/webview/mod.rs on_page_load
156
+
157
+ /// Ruby class for page load events (started/finished).
158
+ /// Matches Tauri's `on_page_load` API.
159
+ #[magnus::wrap(class = "Tokra::Native::PageLoadEvent", free_immediately, size)]
160
+ pub struct RbPageLoadEvent {
161
+ event: String, // "started" or "finished"
162
+ url: String,
163
+ }
164
+
165
+ impl RbPageLoadEvent {
166
+ /// Create a new page load event.
167
+ #[must_use]
168
+ pub const fn new(event: String, url: String) -> Self {
169
+ Self { event, url }
170
+ }
171
+
172
+ /// Returns "started" or "finished"
173
+ #[must_use]
174
+ pub fn event(&self) -> String {
175
+ self.event.clone()
176
+ }
177
+
178
+ /// Get the page URL.
179
+ #[must_use]
180
+ pub fn url(&self) -> String {
181
+ self.url.clone()
182
+ }
183
+
184
+ /// Returns true if this is a "started" event
185
+ #[must_use]
186
+ pub fn started(&self) -> bool {
187
+ self.event == "started"
188
+ }
189
+
190
+ /// Returns true if this is a "finished" event
191
+ #[must_use]
192
+ pub fn finished(&self) -> bool {
193
+ self.event == "finished"
194
+ }
195
+ }
196
+
197
+ // SPDX-SnippetEnd
198
+
199
+ // =============================================================================
200
+ // Module Initialization
201
+ // =============================================================================
202
+
203
+ /// Define all event classes on the given Ruby module.
204
+ ///
205
+ /// # Errors
206
+ ///
207
+ /// Returns an error if class or method definition fails.
208
+ pub fn define_classes(ruby: &Ruby, native: magnus::RModule) -> Result<(), Error> {
209
+ // IpcEvent
210
+ let ipc_event_class = native.define_class("IpcEvent", ruby.class_object())?;
211
+ ipc_event_class.define_singleton_method("new", function!(RbIpcEvent::new, 1))?;
212
+ ipc_event_class.define_method("message", method!(RbIpcEvent::message, 0))?;
213
+
214
+ // WakeUpEvent
215
+ let wake_up_event_class = native.define_class("WakeUpEvent", ruby.class_object())?;
216
+ wake_up_event_class.define_singleton_method("new", function!(RbWakeUpEvent::new, 1))?;
217
+ wake_up_event_class.define_method("payload", method!(RbWakeUpEvent::payload, 0))?;
218
+
219
+ // WindowCloseEvent
220
+ let window_close_event_class = native.define_class("WindowCloseEvent", ruby.class_object())?;
221
+ window_close_event_class
222
+ .define_singleton_method("new", function!(RbWindowCloseEvent::new, 0))?;
223
+
224
+ // HttpRequestEvent
225
+ let http_request_event_class = native.define_class("HttpRequestEvent", ruby.class_object())?;
226
+ http_request_event_class
227
+ .define_singleton_method("new", function!(RbHttpRequestEvent::new, 5))?;
228
+ http_request_event_class
229
+ .define_method("request_id", method!(RbHttpRequestEvent::request_id, 0))?;
230
+ http_request_event_class.define_method("method", method!(RbHttpRequestEvent::method, 0))?;
231
+ http_request_event_class.define_method("uri", method!(RbHttpRequestEvent::uri, 0))?;
232
+ http_request_event_class.define_method("headers", method!(RbHttpRequestEvent::headers, 0))?;
233
+ http_request_event_class.define_method("body", method!(RbHttpRequestEvent::body, 0))?;
234
+
235
+ // PageLoadEvent
236
+ let page_load_event_class = native.define_class("PageLoadEvent", ruby.class_object())?;
237
+ page_load_event_class.define_singleton_method("new", function!(RbPageLoadEvent::new, 2))?;
238
+ page_load_event_class.define_method("event", method!(RbPageLoadEvent::event, 0))?;
239
+ page_load_event_class.define_method("url", method!(RbPageLoadEvent::url, 0))?;
240
+ page_load_event_class.define_method("started?", method!(RbPageLoadEvent::started, 0))?;
241
+ page_load_event_class.define_method("finished?", method!(RbPageLoadEvent::finished, 0))?;
242
+
243
+ Ok(())
244
+ }
245
+
246
+ // =============================================================================
247
+ // Unit Tests
248
+ // =============================================================================
249
+
250
+ #[cfg(test)]
251
+ mod tests {
252
+ use super::*;
253
+
254
+ // -------------------------------------------------------------------------
255
+ // RbIpcEvent tests
256
+ // -------------------------------------------------------------------------
257
+
258
+ #[test]
259
+ fn test_ipc_event_stores_message() {
260
+ let event = RbIpcEvent::new("hello".to_string());
261
+ assert_eq!(event.message(), "hello");
262
+ }
263
+
264
+ #[test]
265
+ fn test_ipc_event_preserves_unicode() {
266
+ let event = RbIpcEvent::new("日本語 🎉".to_string());
267
+ assert_eq!(event.message(), "日本語 🎉");
268
+ }
269
+
270
+ #[test]
271
+ fn test_ipc_event_empty_string() {
272
+ let event = RbIpcEvent::new("".to_string());
273
+ assert!(event.message().is_empty());
274
+ }
275
+
276
+ // -------------------------------------------------------------------------
277
+ // RbWakeUpEvent tests
278
+ // -------------------------------------------------------------------------
279
+
280
+ #[test]
281
+ fn test_wake_up_event_stores_payload() {
282
+ let event = RbWakeUpEvent::new("worker_result".to_string());
283
+ assert_eq!(event.payload(), "worker_result");
284
+ }
285
+
286
+ #[test]
287
+ fn test_wake_up_event_empty_payload() {
288
+ let event = RbWakeUpEvent::new("".to_string());
289
+ assert!(event.payload().is_empty());
290
+ }
291
+
292
+ // -------------------------------------------------------------------------
293
+ // RbWindowCloseEvent tests
294
+ // -------------------------------------------------------------------------
295
+
296
+ #[test]
297
+ fn test_window_close_event_can_be_created() {
298
+ let _event = RbWindowCloseEvent::new();
299
+ // Unit struct - just verify it can be constructed
300
+ }
301
+
302
+ #[test]
303
+ fn test_window_close_event_default_impl() {
304
+ let event = RbWindowCloseEvent::default();
305
+ // Verify Default impl works (produces same as new())
306
+ let _ = event;
307
+ }
308
+
309
+ // -------------------------------------------------------------------------
310
+ // RbHttpRequestEvent tests
311
+ // -------------------------------------------------------------------------
312
+
313
+ #[test]
314
+ fn test_http_request_event_stores_all_fields() {
315
+ let headers = vec![
316
+ ("cookie".to_string(), "session=abc123".to_string()),
317
+ ("accept".to_string(), "text/html".to_string()),
318
+ ];
319
+ let event = RbHttpRequestEvent::new(
320
+ 42,
321
+ "POST".to_string(),
322
+ "tokra://localhost/api".to_string(),
323
+ headers,
324
+ r#"{"key":"value"}"#.to_string(),
325
+ );
326
+
327
+ assert_eq!(event.request_id(), 42);
328
+ assert_eq!(event.method(), "POST");
329
+ assert_eq!(event.uri(), "tokra://localhost/api");
330
+ assert_eq!(event.headers().len(), 2);
331
+ assert_eq!(event.headers()[0].0, "cookie");
332
+ assert_eq!(event.body(), r#"{"key":"value"}"#);
333
+ }
334
+
335
+ #[test]
336
+ fn test_http_request_event_request_id_zero() {
337
+ let event = RbHttpRequestEvent::new(
338
+ 0,
339
+ "GET".to_string(),
340
+ "/".to_string(),
341
+ vec![],
342
+ "".to_string(),
343
+ );
344
+ assert_eq!(event.request_id(), 0);
345
+ }
346
+
347
+ #[test]
348
+ fn test_http_request_event_request_id_max() {
349
+ let event = RbHttpRequestEvent::new(
350
+ u64::MAX,
351
+ "GET".to_string(),
352
+ "/".to_string(),
353
+ vec![],
354
+ "".to_string(),
355
+ );
356
+ assert_eq!(event.request_id(), u64::MAX);
357
+ }
358
+
359
+ #[test]
360
+ fn test_http_request_event_empty_body() {
361
+ let event = RbHttpRequestEvent::new(
362
+ 1,
363
+ "GET".to_string(),
364
+ "/index.html".to_string(),
365
+ vec![],
366
+ "".to_string(),
367
+ );
368
+ assert!(event.body().is_empty());
369
+ assert!(event.headers().is_empty());
370
+ }
371
+
372
+ // -------------------------------------------------------------------------
373
+ // RbPageLoadEvent tests - CRITICAL for mutation testing
374
+ // -------------------------------------------------------------------------
375
+
376
+ #[test]
377
+ fn test_page_load_event_stores_fields() {
378
+ let event = RbPageLoadEvent::new("started".to_string(), "https://example.com/".to_string());
379
+ assert_eq!(event.event(), "started");
380
+ assert_eq!(event.url(), "https://example.com/");
381
+ }
382
+
383
+ #[test]
384
+ fn test_page_load_started_returns_true_for_started() {
385
+ let event = RbPageLoadEvent::new("started".to_string(), "https://example.com/".to_string());
386
+ assert!(event.started());
387
+ }
388
+
389
+ #[test]
390
+ fn test_page_load_started_returns_false_for_finished() {
391
+ let event =
392
+ RbPageLoadEvent::new("finished".to_string(), "https://example.com/".to_string());
393
+ assert!(!event.started());
394
+ }
395
+
396
+ #[test]
397
+ fn test_page_load_finished_returns_true_for_finished() {
398
+ let event =
399
+ RbPageLoadEvent::new("finished".to_string(), "https://example.com/".to_string());
400
+ assert!(event.finished());
401
+ }
402
+
403
+ #[test]
404
+ fn test_page_load_finished_returns_false_for_started() {
405
+ let event = RbPageLoadEvent::new("started".to_string(), "https://example.com/".to_string());
406
+ assert!(!event.finished());
407
+ }
408
+
409
+ // These tests catch mutations that swap "started" <-> "finished"
410
+ #[test]
411
+ fn test_page_load_predicates_are_mutually_exclusive_for_started() {
412
+ let event = RbPageLoadEvent::new("started".to_string(), "https://example.com/".to_string());
413
+ assert!(event.started() && !event.finished());
414
+ }
415
+
416
+ #[test]
417
+ fn test_page_load_predicates_are_mutually_exclusive_for_finished() {
418
+ let event =
419
+ RbPageLoadEvent::new("finished".to_string(), "https://example.com/".to_string());
420
+ assert!(!event.started() && event.finished());
421
+ }
422
+
423
+ // This test catches mutations that return a constant
424
+ #[test]
425
+ fn test_page_load_predicates_both_false_for_unknown() {
426
+ let event = RbPageLoadEvent::new("loading".to_string(), "https://example.com/".to_string());
427
+ assert!(!event.started());
428
+ assert!(!event.finished());
429
+ }
430
+ }