use tagotip_codec::build::{build_ack, build_headless, build_uplink};
use tagotip_codec::inline_vec::InlineVec;
use tagotip_codec::parse::{parse_ack, parse_headless, parse_uplink};
use tagotip_codec::types::*;
const AUTH: &str = "4deedd7bab8817ec";
fn build_to_string(f: impl FnOnce(&mut [u8]) -> Result<usize, tagotip_codec::BuildError>) -> String {
let mut buf = [0u8; 4096];
let n = f(&mut buf).unwrap();
core::str::from_utf8(&buf[..n]).unwrap().to_string()
}
#[test]
fn build_simple_push() {
let mut vars = InlineVec::new();
vars
.push(Variable {
name: "temperature",
operator: Operator::Number,
value: Value::Number("32"),
unit: None,
location: None,
timestamp: None,
group: None,
meta: None,
})
.unwrap();
let frame = UplinkFrame {
method: Method::Push,
seq: None,
auth: AUTH,
serial: "sensor_01",
push_body: Some(PushBody::Structured(StructuredBody {
location: None,
group: None,
timestamp: None,
body_meta: None,
variables: vars,
meta_pool: InlineVec::new(),
})),
pull_body: None,
};
let output = build_to_string(|buf| build_uplink(&frame, buf));
assert_eq!(output, format!("PUSH|{AUTH}|sensor_01|[temperature:=32]"));
}
#[test]
fn build_push_with_seq() {
let mut vars = InlineVec::new();
vars
.push(Variable {
name: "temp",
operator: Operator::Number,
value: Value::Number("25"),
unit: None,
location: None,
timestamp: None,
group: None,
meta: None,
})
.unwrap();
let frame = UplinkFrame {
method: Method::Push,
seq: Some(42),
auth: AUTH,
serial: "sensor_01",
push_body: Some(PushBody::Structured(StructuredBody {
location: None,
group: None,
timestamp: None,
body_meta: None,
variables: vars,
meta_pool: InlineVec::new(),
})),
pull_body: None,
};
let output = build_to_string(|buf| build_uplink(&frame, buf));
assert_eq!(output, format!("PUSH|!42|{AUTH}|sensor_01|[temp:=25]"));
}
#[test]
fn build_ping() {
let frame = UplinkFrame {
method: Method::Ping,
seq: None,
auth: AUTH,
serial: "sensor_01",
push_body: None,
pull_body: None,
};
let output = build_to_string(|buf| build_uplink(&frame, buf));
assert_eq!(output, format!("PING|{AUTH}|sensor_01"));
}
#[test]
fn build_pull() {
let mut vars = InlineVec::new();
vars.push("temperature").unwrap();
vars.push("humidity").unwrap();
let frame = UplinkFrame {
method: Method::Pull,
seq: None,
auth: AUTH,
serial: "sensor_01",
push_body: None,
pull_body: Some(PullBody { variables: vars }),
};
let output = build_to_string(|buf| build_uplink(&frame, buf));
assert_eq!(output, format!("PULL|{AUTH}|sensor_01|[temperature;humidity]"));
}
#[test]
fn build_ack_ok_count() {
let ack = AckFrame {
seq: None,
status: AckStatus::Ok,
detail: Some(AckDetail::Count(3)),
};
let output = build_to_string(|buf| build_ack(&ack, buf));
assert_eq!(output, "ACK|OK|3");
}
#[test]
fn build_ack_pong() {
let ack = AckFrame {
seq: Some(1),
status: AckStatus::Pong,
detail: None,
};
let output = build_to_string(|buf| build_ack(&ack, buf));
assert_eq!(output, "ACK|!1|PONG");
}
#[test]
fn build_ack_err() {
let ack = AckFrame {
seq: Some(5),
status: AckStatus::Err,
detail: Some(AckDetail::Error {
code: ErrorCode::InvalidToken,
text: "invalid_token",
}),
};
let output = build_to_string(|buf| build_ack(&ack, buf));
assert_eq!(output, "ACK|!5|ERR|invalid_token");
}
#[test]
fn build_passthrough_hex() {
let frame = UplinkFrame {
method: Method::Push,
seq: None,
auth: AUTH,
serial: "dev1",
push_body: Some(PushBody::Passthrough(PassthroughBody {
encoding: PassthroughEncoding::Hex,
data: "DEADBEEF",
})),
pull_body: None,
};
let output = build_to_string(|buf| build_uplink(&frame, buf));
assert_eq!(output, format!("PUSH|{AUTH}|dev1|>xDEADBEEF"));
}
#[test]
fn build_headless_push() {
let mut vars = InlineVec::new();
vars
.push(Variable {
name: "temp",
operator: Operator::Number,
value: Value::Number("32"),
unit: None,
location: None,
timestamp: None,
group: None,
meta: None,
})
.unwrap();
let headless = HeadlessFrame {
serial: "sensor_01",
push_body: Some(PushBody::Structured(StructuredBody {
location: None,
group: None,
timestamp: None,
body_meta: None,
variables: vars,
meta_pool: InlineVec::new(),
})),
pull_body: None,
};
let output = build_to_string(|buf| build_headless(Method::Push, &headless, buf));
assert_eq!(output, "sensor_01|[temp:=32]");
}
#[test]
fn build_headless_ping() {
let headless = HeadlessFrame {
serial: "sensor_01",
push_body: None,
pull_body: None,
};
let output = build_to_string(|buf| build_headless(Method::Ping, &headless, buf));
assert_eq!(output, "sensor_01");
}
fn roundtrip_uplink(input: &str) {
let parsed = parse_uplink(input).unwrap();
let output = build_to_string(|buf| build_uplink(&parsed, buf));
assert_eq!(output, input, "roundtrip failed");
}
#[test]
fn roundtrip_simple_push() {
roundtrip_uplink(&format!("PUSH|{AUTH}|sensor_01|[temperature:=32;humidity:=65]"));
}
#[test]
fn roundtrip_push_with_seq() {
roundtrip_uplink(&format!("PUSH|!1|{AUTH}|weather_denver|[temperature:=32;humidity:=65]"));
}
#[test]
fn roundtrip_typed_values() {
roundtrip_uplink(&format!(
"PUSH|{AUTH}|sensor_0a1f|[temperature:=32.5#C;status=online;active?=true]"
));
}
#[test]
fn roundtrip_location() {
roundtrip_uplink(&format!("PUSH|{AUTH}|drone_07|[position@=39.74,-104.99,305]"));
}
#[test]
fn roundtrip_passthrough_hex() {
roundtrip_uplink(&format!("PUSH|{AUTH}|sensor_01|>xDEADBEEF01020304"));
}
#[test]
fn roundtrip_ping() {
roundtrip_uplink(&format!("PING|{AUTH}|sensor_01"));
}
#[test]
fn roundtrip_pull() {
roundtrip_uplink(&format!("PULL|{AUTH}|weather_denver|[temperature;humidity;pressure]"));
}
#[test]
fn roundtrip_ack_ok() {
let input = "ACK|OK|3";
let parsed = parse_ack(input).unwrap();
let output = build_to_string(|buf| build_ack(&parsed, buf));
assert_eq!(output, input);
}
#[test]
fn roundtrip_ack_pong_with_seq() {
let input = "ACK|!1|PONG";
let parsed = parse_ack(input).unwrap();
let output = build_to_string(|buf| build_ack(&parsed, buf));
assert_eq!(output, input);
}
#[test]
fn roundtrip_ack_err() {
let input = "ACK|!5|ERR|invalid_token";
let parsed = parse_ack(input).unwrap();
let output = build_to_string(|buf| build_ack(&parsed, buf));
assert_eq!(output, input);
}
#[test]
fn roundtrip_headless_push() {
let input = "sensor_01|[temp:=32;humidity:=65]";
let parsed = parse_headless(Method::Push, input).unwrap();
let output = build_to_string(|buf| build_headless(Method::Push, &parsed, buf));
assert_eq!(output, input);
}
#[test]
fn roundtrip_body_modifiers() {
roundtrip_uplink(&format!(
"PUSH|{AUTH}|sensor_01|@1694567890000^batch_42{{firmware=2.1}}[temperature:=32#C;humidity:=65#%]"
));
}
#[test]
fn roundtrip_all_suffixes() {
roundtrip_uplink(&format!(
"PUSH|{AUTH}|dev1|[temp:=32.5#C@1694567890000^group1{{source=dht22,quality=high}}]"
));
}
#[test]
fn buffer_too_small_error() {
let frame = UplinkFrame {
method: Method::Ping,
seq: None,
auth: AUTH,
serial: "sensor_01",
push_body: None,
pull_body: None,
};
let mut buf = [0u8; 5];
let result = build_uplink(&frame, &mut buf);
assert!(result.is_err());
}