Skip to content

Commit 76de3fc

Browse files
committed
Added unit tests
1 parent a54752e commit 76de3fc

9 files changed

Lines changed: 799 additions & 19 deletions

File tree

‎.flake8‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ exclude = .git, __pycache__, build, dist, .eggs, .github, .local, docs/,
99
Samples, workers/azure_functions_worker/protos/, workers/proxy_worker/protos/,
1010
workers/azure_functions_worker/_thirdparty/typing_inspect.py,
1111
workers/azure_functions_worker/_vendored/,
12-
workers/rust_worker/bridge/protos/,
12+
workers/r2p2/bridge/protos/,
1313
workers/tests/unittests/test_typing_inspect.py,
1414
workers/tests/unittests/broken_functions/syntax_error/main.py,
1515
.env*, .vscode, venv*, *.venv*,

‎workers/r2p2/Cargo.lock‎

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎workers/r2p2/src/control.rs‎

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,240 @@ pub fn py_to_invocation_response(
404404
result: py_to_status_result(get_child(d, "result")),
405405
})
406406
}
407+
408+
#[cfg(test)]
409+
mod tests {
410+
use super::*;
411+
use crate::pb::messages::{
412+
parameter_binding, streaming_message::Content, BindingInfo, FunctionLoadRequest,
413+
InvocationRequest, ParameterBinding, RpcFunctionMetadata, TypedData, WorkerInitRequest,
414+
};
415+
use crate::pb::messages::{streaming_message, typed_data};
416+
use pyo3::types::{PyDict, PyList, PyString, PyTuple};
417+
418+
fn datum_tuple<'py>(py: Python<'py>, kind: &str, value: &str) -> Bound<'py, PyAny> {
419+
PyTuple::new(
420+
py,
421+
vec![
422+
PyString::new(py, kind).into_any(),
423+
PyString::new(py, value).into_any(),
424+
],
425+
)
426+
.unwrap()
427+
.into_any()
428+
}
429+
430+
// --- pure (no interpreter) -------------------------------------------
431+
#[test]
432+
fn start_stream_carries_worker_id_and_empty_request_id() {
433+
let msg = start_stream_message("worker-123");
434+
assert_eq!(msg.request_id, "");
435+
match msg.content {
436+
Some(Content::StartStream(s)) => assert_eq!(s.worker_id, "worker-123"),
437+
other => panic!("expected StartStream, got {other:?}"),
438+
}
439+
}
440+
441+
// --- inbound request (prost -> dict) ---------------------------------
442+
#[test]
443+
fn worker_init_request_decodes_to_dict() {
444+
Python::attach(|py| {
445+
let mut caps = std::collections::HashMap::new();
446+
caps.insert("RawHttpBodyBytes".to_string(), "true".to_string());
447+
let r = WorkerInitRequest {
448+
function_app_directory: "/home/site/wwwroot".into(),
449+
host_version: "4.1.0".into(),
450+
capabilities: caps,
451+
..Default::default()
452+
};
453+
let d = worker_init_req_to_py(py, &r).unwrap();
454+
let dir: String = d
455+
.get_item("function_app_directory")
456+
.unwrap()
457+
.unwrap()
458+
.extract()
459+
.unwrap();
460+
assert_eq!(dir, "/home/site/wwwroot");
461+
let hv: String = d
462+
.get_item("host_version")
463+
.unwrap()
464+
.unwrap()
465+
.extract()
466+
.unwrap();
467+
assert_eq!(hv, "4.1.0");
468+
});
469+
}
470+
471+
#[test]
472+
fn function_load_request_decodes_nested_bindings() {
473+
Python::attach(|py| {
474+
let mut bindings = std::collections::HashMap::new();
475+
bindings.insert(
476+
"req".to_string(),
477+
BindingInfo {
478+
r#type: "httpTrigger".into(),
479+
direction: 0,
480+
data_type: 0,
481+
..Default::default()
482+
},
483+
);
484+
let r = FunctionLoadRequest {
485+
function_id: "fid-1".into(),
486+
metadata: Some(RpcFunctionMetadata {
487+
name: "hello".into(),
488+
entry_point: "main".into(),
489+
bindings,
490+
..Default::default()
491+
}),
492+
..Default::default()
493+
};
494+
let d = function_load_req_to_py(py, &r).unwrap();
495+
let fid: String = d
496+
.get_item("function_id")
497+
.unwrap()
498+
.unwrap()
499+
.extract()
500+
.unwrap();
501+
assert_eq!(fid, "fid-1");
502+
let meta = d.get_item("metadata").unwrap().unwrap();
503+
let name: String = meta.get_item("name").unwrap().extract().unwrap();
504+
assert_eq!(name, "hello");
505+
let btype: String = meta
506+
.get_item("bindings")
507+
.unwrap()
508+
.get_item("req")
509+
.unwrap()
510+
.get_item("type")
511+
.unwrap()
512+
.extract()
513+
.unwrap();
514+
assert_eq!(btype, "httpTrigger");
515+
});
516+
}
517+
518+
#[test]
519+
fn invocation_request_decodes_inputs_and_metadata() {
520+
Python::attach(|py| {
521+
let mut trigger_metadata = std::collections::HashMap::new();
522+
trigger_metadata.insert(
523+
"Query".to_string(),
524+
TypedData {
525+
data: Some(typed_data::Data::String("q".into())),
526+
},
527+
);
528+
let r = InvocationRequest {
529+
invocation_id: "inv-1".into(),
530+
function_id: "fid-1".into(),
531+
input_data: vec![ParameterBinding {
532+
name: "req".into(),
533+
rpc_data: Some(parameter_binding::RpcData::Data(TypedData {
534+
data: Some(typed_data::Data::String("body".into())),
535+
})),
536+
}],
537+
trigger_metadata,
538+
..Default::default()
539+
};
540+
let d = invocation_request_to_py(py, &r).unwrap();
541+
let inv: String = d
542+
.get_item("invocation_id")
543+
.unwrap()
544+
.unwrap()
545+
.extract()
546+
.unwrap();
547+
assert_eq!(inv, "inv-1");
548+
let inputs = d.get_item("input_data").unwrap().unwrap();
549+
let first = inputs.get_item(0).unwrap();
550+
let name: String = first.get_item(0).unwrap().extract().unwrap();
551+
assert_eq!(name, "req");
552+
let datum_kind: String = first
553+
.get_item(1)
554+
.unwrap()
555+
.get_item(0)
556+
.unwrap()
557+
.extract()
558+
.unwrap();
559+
assert_eq!(datum_kind, "string");
560+
});
561+
}
562+
563+
// --- adapter response dict (dict -> prost) ---------------------------
564+
#[test]
565+
fn worker_init_response_maps_capabilities_and_status() {
566+
Python::attach(|py| {
567+
let d = PyDict::new(py);
568+
let caps = PyDict::new(py);
569+
caps.set_item("WorkerStatus", "true").unwrap();
570+
d.set_item("capabilities", caps).unwrap();
571+
let result = PyDict::new(py);
572+
result.set_item("status", 1).unwrap();
573+
d.set_item("result", result).unwrap();
574+
575+
let resp = py_to_worker_init_response(&d.into_any()).unwrap();
576+
assert_eq!(
577+
resp.capabilities.get("WorkerStatus").map(String::as_str),
578+
Some("true")
579+
);
580+
assert_eq!(resp.result.unwrap().status, 1);
581+
});
582+
}
583+
584+
#[test]
585+
fn function_load_response_maps_id_and_status() {
586+
Python::attach(|py| {
587+
let d = PyDict::new(py);
588+
d.set_item("function_id", "fid-1").unwrap();
589+
let result = PyDict::new(py);
590+
result.set_item("status", 1).unwrap();
591+
d.set_item("result", result).unwrap();
592+
593+
let resp = py_to_function_load_response(&d.into_any()).unwrap();
594+
assert_eq!(resp.function_id, "fid-1");
595+
assert_eq!(resp.result.unwrap().status, 1);
596+
});
597+
}
598+
599+
#[test]
600+
fn invocation_response_maps_return_value_and_result() {
601+
Python::attach(|py| {
602+
let d = PyDict::new(py);
603+
d.set_item("return_value", datum_tuple(py, "string", "ok"))
604+
.unwrap();
605+
d.set_item("output_data", PyList::empty(py)).unwrap();
606+
let result = PyDict::new(py);
607+
result.set_item("status", 1).unwrap();
608+
d.set_item("result", result).unwrap();
609+
610+
let resp = py_to_invocation_response(py, &d.into_any(), "inv-1").unwrap();
611+
assert_eq!(resp.invocation_id, "inv-1");
612+
assert_eq!(resp.result.unwrap().status, 1);
613+
match resp.return_value.and_then(|t| t.data) {
614+
Some(typed_data::Data::String(s)) => assert_eq!(s, "ok"),
615+
other => panic!("unexpected return_value: {other:?}"),
616+
}
617+
});
618+
}
619+
620+
// --- logging ----------------------------------------------------------
621+
#[test]
622+
fn log_dict_builds_rpc_log_streaming_message() {
623+
Python::attach(|py| {
624+
let d = PyDict::new(py);
625+
d.set_item("invocation_id", "inv-1").unwrap();
626+
d.set_item("category", "worker").unwrap();
627+
d.set_item("level", 4).unwrap();
628+
d.set_item("message", "hello logs").unwrap();
629+
630+
let msg = py_log_to_streaming_message(&d.into_any(), "req-9").unwrap();
631+
assert_eq!(msg.request_id, "req-9");
632+
match msg.content {
633+
Some(streaming_message::Content::RpcLog(l)) => {
634+
assert_eq!(l.invocation_id, "inv-1");
635+
assert_eq!(l.category, "worker");
636+
assert_eq!(l.level, 4);
637+
assert_eq!(l.message, "hello logs");
638+
}
639+
other => panic!("expected RpcLog, got {other:?}"),
640+
}
641+
});
642+
}
643+
}

0 commit comments

Comments
 (0)