From dora-skills
Provides built-in integration testing for dora nodes, supporting JSONL input/output, environment variable configuration, and Rust test harnesses.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dora-skills:integration-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Built-in support for testing dora nodes
Built-in support for testing dora nodes
Dora provides integration testing capabilities that allow testing nodes in isolation without running the full daemon.
use dora_node_api::integration_testing::{
setup_integration_testing, TestingInput, TestingOutput, TestingOptions
};
#[test]
fn test_my_node() {
// Setup test environment
setup_integration_testing(
TestingInput::FromJsonFile("tests/inputs.jsonl".into()),
TestingOutput::ToFile("tests/outputs.jsonl".into()),
TestingOptions::default(),
);
// Run your node normally
let (mut node, mut events) = DoraNode::init_from_env().unwrap();
// Node will receive inputs from the test file
while let Some(event) = events.recv() {
// ... normal processing
}
}
{"id": "tick", "data": null, "time_offset_secs": 0.0}
{"id": "image", "data": [1, 2, 3, 4], "time_offset_secs": 0.1}
{"id": "config", "data": {"width": 640, "height": 480}, "time_offset_secs": 0.2}
{"type": "Stop", "time_offset_secs": 1.0}
// Null data (like timer tick)
{"id": "tick", "data": null, "time_offset_secs": 0.0}
// Array data
{"id": "numbers", "data": [1, 2, 3], "time_offset_secs": 0.1}
// String data
{"id": "text", "data": "hello world", "time_offset_secs": 0.2}
// Object/struct data
{"id": "config", "data": {"key": "value"}, "time_offset_secs": 0.3}
// Binary data (base64 encoded)
{"id": "image", "data": "base64:SGVsbG8=", "time_offset_secs": 0.4}
// Stop event
{"type": "Stop", "time_offset_secs": 1.0}
// Input closed event
{"type": "InputClosed", "id": "tick", "time_offset_secs": 0.5}
let options = TestingOptions {
skip_output_time_offsets: true, // Don't record time offsets in outputs
};
Alternatively, use environment variables:
# Set input file
export DORA_TEST_WITH_INPUTS=tests/inputs.jsonl
# Optional: set output file
export DORA_TEST_WRITE_OUTPUTS_TO=tests/outputs.jsonl
# Optional: skip time offsets
export DORA_TEST_NO_OUTPUT_TIME_OFFSET=1
# Run your node
./my_node
The test framework writes outputs to a JSONL file:
{"id": "result", "data": [42], "time_offset_secs": 0.05}
{"id": "status", "data": "ok", "time_offset_secs": 0.1}
#[test]
fn verify_outputs() {
// Run test...
// Read outputs
let outputs = std::fs::read_to_string("tests/outputs.jsonl").unwrap();
let lines: Vec<&str> = outputs.lines().collect();
// Parse and verify
let first: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
assert_eq!(first["id"], "result");
assert_eq!(first["data"], json!([42]));
}
# test_my_node.py
import subprocess
import json
import os
def test_my_node():
# Create test inputs
inputs = [
{"id": "tick", "data": None, "time_offset_secs": 0.0},
{"id": "data", "data": [1, 2, 3], "time_offset_secs": 0.1},
{"type": "Stop", "time_offset_secs": 1.0},
]
with open("test_inputs.jsonl", "w") as f:
for inp in inputs:
f.write(json.dumps(inp) + "\n")
# Set environment and run
env = os.environ.copy()
env["DORA_TEST_WITH_INPUTS"] = "test_inputs.jsonl"
env["DORA_TEST_WRITE_OUTPUTS_TO"] = "test_outputs.jsonl"
result = subprocess.run(
["python", "my_node.py"],
env=env,
capture_output=True
)
assert result.returncode == 0
# Verify outputs
with open("test_outputs.jsonl") as f:
outputs = [json.loads(line) for line in f]
assert outputs[0]["id"] == "result"
// src/main.rs
use dora_node_api::{DoraNode, Event, MetadataParameters};
use dora_node_api::arrow::array::Int32Array;
fn main() -> eyre::Result<()> {
let (mut node, mut events) = DoraNode::init_from_env()?;
let mut sum = 0i32;
while let Some(event) = events.recv() {
match event {
Event::Input { id, data, .. } => {
if id.as_ref() == "number" {
if let Some(arr) = data.as_any().downcast_ref::<Int32Array>() {
sum += arr.value(0);
let result = Int32Array::from(vec![sum]);
node.send_output(
"sum".into(),
MetadataParameters::default(),
result,
)?;
}
}
}
Event::Stop(_) => break,
_ => {}
}
}
Ok(())
}
// tests/integration.rs
use dora_node_api::DoraNode;
use dora_node_api::integration_testing::*;
#[test]
fn test_sum_node() {
setup_integration_testing(
TestingInput::FromJsonFile("tests/sum_inputs.jsonl".into()),
TestingOutput::ToFile("tests/sum_outputs.jsonl".into()),
TestingOptions::default(),
);
// Run node (this would be in a subprocess in real test)
// ...
// Verify outputs
let outputs = std::fs::read_to_string("tests/sum_outputs.jsonl").unwrap();
// Parse and assert...
}
{"id": "number", "data": [10], "time_offset_secs": 0.0}
{"id": "number", "data": [20], "time_offset_secs": 0.1}
{"id": "number", "data": [30], "time_offset_secs": 0.2}
{"type": "Stop", "time_offset_secs": 1.0}
{"id": "sum", "data": [10], "time_offset_secs": 0.01}
{"id": "sum", "data": [30], "time_offset_secs": 0.11}
{"id": "sum", "data": [60], "time_offset_secs": 0.21}
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub zhanghandong/dora-skills --plugin dora-skills