Skip to content
File

Blob: src/rust/worker/test.c++

9.5 KB
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include <workerd/io/frankenvalue.h>
6#include <workerd/io/io-context.h>
7#include <workerd/io/worker.h>
8#include <workerd/rust/worker/bridge.h>
9#include <workerd/rust/worker/error.rs.h>
10#include <workerd/rust/worker/kill_switch.rs.h>
11#include <workerd/rust/worker/ok.rs.h>
12#include <workerd/util/exception.h>
13 
14#include <kj-rs/kj-rs.h>
15 
16#include <kj/async.h>
17#include <kj/compat/http.h>
18#include <kj/test.h>
19 
20using namespace workerd::rust::worker;
21using namespace kj_rs;
22 
23namespace workerd {
24namespace {
25 
26kj::Own<kj::HttpClient> newClient(::rust::Box<RustWorkerInterface::Impl> impl) {
27 auto worker = kj::from<Rust>(kj::mv(impl));
28 return kj::newHttpClient(*worker).attach(kj::mv(worker));
29}
30 
31KJ_TEST("kill_switch worker") {
32 kj::EventLoop loop;
33 kj::WaitScope waitScope(loop);
34 
35 auto client = newClient(new_kill_switch_worker());
36 kj::HttpHeaderTable headerTable;
37 kj::HttpHeaders headers(headerTable);
38 auto exception = kj::runCatchingExceptions([&]() {
39 client->request(kj::HttpMethod::GET, "http://test/"_kj, headers).response.wait(waitScope);
40 });
41 
42 auto& e = KJ_ASSERT_NONNULL(exception);
43 KJ_ASSERT(e.getType() == kj::Exception::Type::OVERLOADED);
44 KJ_ASSERT(e.getDescription() == "jsg.Error: This script has been killed.");
45 KJ_ASSERT(e.getDetail(SCRIPT_KILLED_DETAIL_ID) != kj::none);
46}
47 
48KJ_TEST("ok worker request") {
49 kj::EventLoop loop;
50 kj::WaitScope waitScope(loop);
51 
52 auto client = newClient(new_ok_worker());
53 kj::HttpHeaderTable headerTable;
54 kj::HttpHeaders headers(headerTable);
55 
56 auto response =
57 client->request(kj::HttpMethod::GET, "http://test/"_kj, headers).response.wait(waitScope);
58 
59 KJ_ASSERT(response.statusCode == 200);
60 KJ_ASSERT(response.statusText == "OK");
61 KJ_ASSERT(response.body->readAllText().wait(waitScope) == "OK");
62}
63 
64KJ_TEST("kill_switch worker connect") {
65 kj::EventLoop loop;
66 kj::WaitScope waitScope(loop);
67 
68 auto client = newClient(new_kill_switch_worker());
69 kj::HttpHeaderTable headerTable;
70 kj::HttpHeaders headers(headerTable);
71 auto exception = kj::runCatchingExceptions([&]() {
72 client->connect("example.com"_kj, headers, kj::HttpConnectSettings{}).status.wait(waitScope);
73 });
74 
75 auto& e = KJ_ASSERT_NONNULL(exception);
76 KJ_ASSERT(e.getType() == kj::Exception::Type::OVERLOADED);
77 KJ_ASSERT(e.getDescription() == "jsg.Error: This script has been killed.");
78 KJ_ASSERT(e.getDetail(SCRIPT_KILLED_DETAIL_ID) != kj::none);
79}
80 
81KJ_TEST("kill_switch worker prewarm") {
82 kj::EventLoop loop;
83 kj::WaitScope waitScope(loop);
84 
85 auto worker = kj::from<Rust>(new_kill_switch_worker());
86 
87 // Prewarm should succeed (default implementation)
88 kj::StringPtr url = "/test";
89 
90 // Should not throw
91 worker->prewarm(url).wait(waitScope);
92}
93 
94KJ_TEST("kill_switch worker runScheduled") {
95 kj::EventLoop loop;
96 kj::WaitScope waitScope(loop);
97 
98 auto worker = kj::from<Rust>(new_kill_switch_worker());
99 
100 kj::Date scheduledTime = kj::UNIX_EPOCH + 1000 * kj::SECONDS;
101 kj::StringPtr cron = "0 0 * * *";
102 
103 auto result = worker->runScheduled(scheduledTime, cron).wait(waitScope);
104 
105 KJ_ASSERT(result.retry == false);
106 KJ_ASSERT(result.outcome == workerd::EventOutcome::KILL_SWITCH);
107}
108 
109KJ_TEST("kill_switch worker runAlarm") {
110 kj::EventLoop loop;
111 kj::WaitScope waitScope(loop);
112 
113 auto worker = kj::from<Rust>(new_kill_switch_worker());
114 
115 kj::Date scheduledTime = kj::UNIX_EPOCH + 2000 * kj::SECONDS;
116 uint32_t retryCount = 3;
117 
118 auto result = worker->runAlarm(scheduledTime, retryCount).wait(waitScope);
119 
120 KJ_ASSERT(result.retry == false);
121 KJ_ASSERT(result.retryCountsAgainstLimit == true);
122 KJ_ASSERT(result.outcome == workerd::EventOutcome::KILL_SWITCH);
123}
124 
125KJ_TEST("kill_switch worker customEvent") {
126 kj::EventLoop loop;
127 kj::WaitScope waitScope(loop);
128 
129 auto worker = kj::from<Rust>(new_kill_switch_worker());
130 
131 // Create a minimal custom event mock
132 class TestCustomEvent: public workerd::WorkerInterface::CustomEvent {
133 public:
134 virtual ~TestCustomEvent() = default;
135 
136 kj::Promise<Result> run(kj::Own<workerd::IoContext_IncomingRequest> incomingRequest,
137 kj::Maybe<kj::StringPtr> entrypointName,
138 kj::Maybe<workerd::Worker::VersionInfo> versionInfo,
139 workerd::Frankenvalue props,
140 kj::TaskSet& waitUntilTasks,
141 bool) override {
142 KJ_UNIMPLEMENTED();
143 }
144 
145 kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,
146 capnp::ByteStreamFactory& byteStreamFactory,
147 workerd::rpc::EventDispatcher::Client dispatcher) override {
148 KJ_UNIMPLEMENTED();
149 }
150 
151 kj::Promise<Result> notSupported() override {
152 KJ_UNIMPLEMENTED();
153 };
154 
155 uint16_t getType() override {
156 return 42;
157 }
158 
159 workerd::tracing::EventInfo getEventInfo() const override {
160 return workerd::tracing::CustomEventInfo();
161 }
162 };
163 
164 auto event = kj::heap<TestCustomEvent>();
165 
166 auto result = worker->customEvent(kj::mv(event)).wait(waitScope);
167 
168 KJ_ASSERT(result.outcome == workerd::EventOutcome::KILL_SWITCH);
169}
170 
171KJ_TEST("kill_switch worker test") {
172 kj::EventLoop loop;
173 kj::WaitScope waitScope(loop);
174 
175 auto worker = kj::from<Rust>(new_kill_switch_worker());
176 
177 auto p = worker->test();
178 bool result = p.wait(waitScope);
179 
180 KJ_ASSERT(result == false);
181}
182 
183KJ_TEST("error worker request") {
184 kj::EventLoop loop;
185 kj::WaitScope waitScope(loop);
186 
187 auto client = newClient(new_error_worker("Test error message"));
188 kj::HttpHeaderTable headerTable;
189 kj::HttpHeaders headers(headerTable);
190 auto exception = kj::runCatchingExceptions([&]() {
191 client->request(kj::HttpMethod::GET, "http://test/test"_kj, headers).response.wait(waitScope);
192 });
193 
194 auto& e = KJ_ASSERT_NONNULL(exception);
195 KJ_ASSERT(e.getType() == kj::Exception::Type::FAILED);
196 KJ_ASSERT(e.getDescription() == "jsg.Error: Test error message");
197}
198 
199KJ_TEST("error worker connect") {
200 kj::EventLoop loop;
201 kj::WaitScope waitScope(loop);
202 
203 auto client = newClient(new_error_worker("Connection failed"));
204 kj::HttpHeaderTable headerTable;
205 kj::HttpHeaders headers(headerTable);
206 auto exception = kj::runCatchingExceptions([&]() {
207 client->connect("example.com"_kj, headers, kj::HttpConnectSettings{}).status.wait(waitScope);
208 });
209 
210 auto& e = KJ_ASSERT_NONNULL(exception);
211 KJ_ASSERT(e.getType() == kj::Exception::Type::FAILED);
212 KJ_ASSERT(e.getDescription() == "jsg.Error: Connection failed");
213}
214 
215KJ_TEST("error worker runScheduled") {
216 kj::EventLoop loop;
217 kj::WaitScope waitScope(loop);
218 
219 auto worker = kj::from<Rust>(new_error_worker("Scheduled task failed"));
220 
221 kj::Date scheduledTime = kj::UNIX_EPOCH + 1000 * kj::SECONDS;
222 kj::StringPtr cron = "0 0 * * *";
223 
224 kj::Maybe<kj::Exception> exception;
225 
226 try {
227 worker->runScheduled(scheduledTime, cron).wait(waitScope);
228 } catch (...) {
229 exception = kj::getCaughtExceptionAsKj();
230 }
231 
232 auto& e = KJ_ASSERT_NONNULL(exception);
233 KJ_ASSERT(e.getType() == kj::Exception::Type::FAILED);
234 KJ_ASSERT(e.getDescription() == "jsg.Error: Scheduled task failed");
235}
236 
237KJ_TEST("error worker runAlarm") {
238 kj::EventLoop loop;
239 kj::WaitScope waitScope(loop);
240 
241 auto worker = kj::from<Rust>(new_error_worker("Alarm execution failed"));
242 
243 kj::Date scheduledTime = kj::UNIX_EPOCH + 2000 * kj::SECONDS;
244 uint32_t retryCount = 5;
245 
246 kj::Maybe<kj::Exception> exception;
247 
248 try {
249 worker->runAlarm(scheduledTime, retryCount).wait(waitScope);
250 } catch (...) {
251 exception = kj::getCaughtExceptionAsKj();
252 }
253 
254 auto& e = KJ_ASSERT_NONNULL(exception);
255 KJ_ASSERT(e.getType() == kj::Exception::Type::FAILED);
256 KJ_ASSERT(e.getDescription() == "jsg.Error: Alarm execution failed");
257}
258 
259KJ_TEST("error worker customEvent") {
260 kj::EventLoop loop;
261 kj::WaitScope waitScope(loop);
262 
263 auto worker = kj::from<Rust>(new_error_worker("Custom event error"));
264 
265 // Create a minimal custom event mock
266 class TestCustomEvent: public workerd::WorkerInterface::CustomEvent {
267 public:
268 virtual ~TestCustomEvent() = default;
269 
270 kj::Promise<workerd::WorkerInterface::CustomEvent::Result> run(
271 kj::Own<workerd::IoContext_IncomingRequest> incomingRequest,
272 kj::Maybe<kj::StringPtr> entrypointName,
273 kj::Maybe<workerd::Worker::VersionInfo> versionInfo,
274 workerd::Frankenvalue props,
275 kj::TaskSet& waitUntilTasks,
276 bool) override {
277 KJ_UNIMPLEMENTED();
278 }
279 
280 kj::Promise<workerd::WorkerInterface::CustomEvent::Result> sendRpc(
281 capnp::HttpOverCapnpFactory& httpOverCapnpFactory,
282 capnp::ByteStreamFactory& byteStreamFactory,
283 workerd::rpc::EventDispatcher::Client dispatcher) override {
284 KJ_UNIMPLEMENTED();
285 }
286 
287 kj::Promise<workerd::WorkerInterface::CustomEvent::Result> notSupported() override {
288 KJ_UNIMPLEMENTED();
289 };
290 
291 uint16_t getType() override {
292 return 123;
293 }
294 
295 workerd::tracing::EventInfo getEventInfo() const override {
296 return workerd::tracing::CustomEventInfo();
297 }
298 };
299 
300 auto event = kj::heap<TestCustomEvent>();
301 
302 kj::Maybe<kj::Exception> exception;
303 
304 try {
305 worker->customEvent(kj::mv(event)).wait(waitScope);
306 } catch (...) {
307 exception = kj::getCaughtExceptionAsKj();
308 }
309 
310 auto& e = KJ_ASSERT_NONNULL(exception);
311 KJ_ASSERT(e.getType() == kj::Exception::Type::FAILED);
312 KJ_ASSERT(e.getDescription() == "jsg.Error: Custom event error");
313}
314 
315KJ_TEST("error worker test") {
316 kj::EventLoop loop;
317 kj::WaitScope waitScope(loop);
318 
319 auto worker = kj::from<Rust>(new_error_worker("Test method failed"));
320 
321 KJ_ASSERT(!worker->test().wait(waitScope));
322}
323 
324} // namespace
325} // namespace workerd