1use crate::actions::ArtifactValue;
7use crate::artifact_resolver::resolve_artifact_bytes;
8use crate::executor::ExecutionError;
9use crate::state::HandlerContext;
10use base16ct::lower::encode_string as hex_encode_string;
11use base32ct::{Base32Unpadded, Encoding as Base32Encoding};
12use base64ct::{Base64, Encoding as Base64Encoding};
13use rite_model::expression::{
14 ExprValue, Expression, Literal, PipeStage, RefType, Reference, StringPart, Value,
15};
16use rite_model::{ArtifactId, ParamId};
17use sha2::{Digest, Sha256, Sha384, Sha512};
18
19pub fn evaluate(expr: &Expression, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
29 match expr {
30 Expression::Reference(ref_) => evaluate_reference(ref_, ctx),
31 Expression::Literal(lit) => Ok(evaluate_literal(lit)),
32 Expression::Pipeline { source, stages } => {
33 let mut value = evaluate(source, ctx)?;
34 for stage in stages {
35 value = apply_pipe_stage(&value, stage, ctx)?;
36 }
37 Ok(value)
38 }
39 }
40}
41
42fn evaluate_reference(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
44 match ref_.ref_type {
45 RefType::Param => evaluate_param_ref(ref_, ctx),
46 RefType::Artifact => evaluate_artifact_ref(ref_, ctx),
47 RefType::Role => Ok(Value::String(ref_.name.clone())),
50 }
51}
52
53fn evaluate_param_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
55 let param_id = ParamId::new(&ref_.name);
56 let value = ctx.params.get(¶m_id).ok_or_else(|| {
57 ExecutionError::InvalidParams(format!("Unknown parameter: {}", ref_.name))
58 })?;
59
60 json_to_value(value)
61}
62
63fn json_to_value(json: &serde_json::Value) -> Result<Value, ExecutionError> {
65 match json {
66 serde_json::Value::String(s) => Ok(Value::String(s.clone())),
67 serde_json::Value::Number(n) => {
68 if let Some(i) = n.as_i64() {
69 Ok(Value::Integer(i))
70 } else if let Some(f) = n.as_f64() {
71 #[allow(clippy::cast_possible_truncation)]
72 Ok(Value::Integer(f as i64))
73 } else {
74 Err(ExecutionError::InvalidParams(
75 "Number too large".to_string(),
76 ))
77 }
78 }
79 serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
80 serde_json::Value::Null => Ok(Value::Null),
81 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
82 Err(ExecutionError::InvalidParams(
83 "Cannot use array or object as expression value".to_string(),
84 ))
85 }
86 }
87}
88
89fn evaluate_artifact_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
91 let artifact_id = ArtifactId::new(&ref_.name);
92 let artifact = ctx.get_artifact(&artifact_id).ok_or_else(|| {
93 ExecutionError::InvalidParams(format!("Artifact '{}' not found", ref_.name))
94 })?;
95
96 match artifact {
97 ArtifactValue::Text(text) => {
99 if let Some(property) = &ref_.property {
100 return Err(ExecutionError::InvalidParams(format!(
101 "Property access '.{property}' not supported for text artifacts"
102 )));
103 }
104 Ok(Value::String(text.clone()))
105 }
106
107 ArtifactValue::Bytes(bytes) => {
109 if let Some(property) = &ref_.property {
111 return Err(ExecutionError::InvalidParams(format!(
112 "Property access '.{property}' not supported for binary artifacts"
113 )));
114 }
115 Ok(Value::Bytes(bytes.clone()))
116 }
117
118 _ => {
120 let bytes =
121 resolve_artifact_bytes(ctx.artifacts, &artifact_id, ref_.property.as_deref())?;
122 Ok(Value::Bytes(bytes))
123 }
124 }
125}
126
127fn evaluate_literal(lit: &Literal) -> Value {
129 match lit {
130 Literal::String(s) => Value::String(s.clone()),
131 Literal::Integer(i) => Value::Integer(*i),
132 #[allow(clippy::cast_possible_truncation)]
133 Literal::Float(f) => Value::Integer(*f as i64), Literal::Boolean(b) => Value::Boolean(*b),
135 Literal::Null => Value::Null,
136 }
137}
138
139fn apply_pipe_stage(
141 input: &Value,
142 stage: &PipeStage,
143 ctx: &HandlerContext,
144) -> Result<Value, ExecutionError> {
145 match stage.function.as_str() {
146 "sha256" => apply_sha256(input),
148 "sha384" => apply_sha384(input),
149 "sha512" => apply_sha512(input),
150
151 "hex" => apply_hex(input),
153 "base32" => apply_base32(input),
154 "base64" => apply_base64(input),
155
156 "upper" => apply_upper(input),
158 "lower" => apply_lower(input),
159 "substr" => apply_substr(input, &stage.args, ctx),
160
161 "concat" => apply_concat(&stage.args, ctx),
163
164 unknown => Err(ExecutionError::InvalidParams(format!(
165 "Unknown function: {unknown}"
166 ))),
167 }
168}
169
170fn apply_sha256(input: &Value) -> Result<Value, ExecutionError> {
171 let bytes = input.as_bytes().ok_or_else(|| {
172 ExecutionError::InvalidParams(format!(
173 "sha256 expects bytes or string, got {}",
174 input.type_name()
175 ))
176 })?;
177
178 let mut hasher = Sha256::new();
179 hasher.update(&bytes);
180 let result = hasher.finalize();
181
182 Ok(Value::Bytes(result.to_vec()))
183}
184
185fn apply_sha384(input: &Value) -> Result<Value, ExecutionError> {
186 let bytes = input.as_bytes().ok_or_else(|| {
187 ExecutionError::InvalidParams(format!(
188 "sha384 expects bytes or string, got {}",
189 input.type_name()
190 ))
191 })?;
192
193 let mut hasher = Sha384::new();
194 hasher.update(&bytes);
195 let result = hasher.finalize();
196
197 Ok(Value::Bytes(result.to_vec()))
198}
199
200fn apply_sha512(input: &Value) -> Result<Value, ExecutionError> {
201 let bytes = input.as_bytes().ok_or_else(|| {
202 ExecutionError::InvalidParams(format!(
203 "sha512 expects bytes or string, got {}",
204 input.type_name()
205 ))
206 })?;
207
208 let mut hasher = Sha512::new();
209 hasher.update(&bytes);
210 let result = hasher.finalize();
211
212 Ok(Value::Bytes(result.to_vec()))
213}
214
215fn apply_hex(input: &Value) -> Result<Value, ExecutionError> {
216 let bytes = input.as_bytes().ok_or_else(|| {
217 ExecutionError::InvalidParams(format!(
218 "hex expects bytes or string, got {}",
219 input.type_name()
220 ))
221 })?;
222
223 let hex_string = hex_encode(&bytes);
224 Ok(Value::String(hex_string))
225}
226
227fn apply_base32(input: &Value) -> Result<Value, ExecutionError> {
228 let bytes = input.as_bytes().ok_or_else(|| {
229 ExecutionError::InvalidParams(format!(
230 "base32 expects bytes or string, got {}",
231 input.type_name()
232 ))
233 })?;
234
235 let encoded = Base32Unpadded::encode_string(&bytes).to_uppercase();
237 Ok(Value::String(encoded))
238}
239
240fn apply_base64(input: &Value) -> Result<Value, ExecutionError> {
241 let bytes = input.as_bytes().ok_or_else(|| {
242 ExecutionError::InvalidParams(format!(
243 "base64 expects bytes or string, got {}",
244 input.type_name()
245 ))
246 })?;
247
248 let encoded = Base64::encode_string(&bytes);
249 Ok(Value::String(encoded))
250}
251
252fn hex_encode(bytes: &[u8]) -> String {
254 hex_encode_string(bytes)
255}
256
257fn apply_upper(input: &Value) -> Result<Value, ExecutionError> {
258 let s = input.as_string().ok_or_else(|| {
259 ExecutionError::InvalidParams(format!("upper expects string, got {}", input.type_name()))
260 })?;
261
262 Ok(Value::String(s.to_uppercase()))
263}
264
265fn apply_lower(input: &Value) -> Result<Value, ExecutionError> {
266 let s = input.as_string().ok_or_else(|| {
267 ExecutionError::InvalidParams(format!("lower expects string, got {}", input.type_name()))
268 })?;
269
270 Ok(Value::String(s.to_lowercase()))
271}
272
273fn apply_substr(
274 input: &Value,
275 args: &[Expression],
276 ctx: &HandlerContext,
277) -> Result<Value, ExecutionError> {
278 let s = input.as_string().ok_or_else(|| {
279 ExecutionError::InvalidParams(format!("substr expects string, got {}", input.type_name()))
280 })?;
281
282 let [start_arg, len_arg] = args else {
283 return Err(ExecutionError::InvalidParams(
284 "substr requires 2 arguments: start and length".to_string(),
285 ));
286 };
287
288 let start_i64 = evaluate(start_arg, ctx)?
289 .as_integer()
290 .ok_or_else(|| ExecutionError::InvalidParams("substr start must be integer".to_string()))?;
291
292 let len_i64 = evaluate(len_arg, ctx)?.as_integer().ok_or_else(|| {
293 ExecutionError::InvalidParams("substr length must be integer".to_string())
294 })?;
295
296 let start = usize::try_from(start_i64.max(0)).unwrap_or(0).min(s.len());
298 let len = usize::try_from(len_i64.max(0)).unwrap_or(0);
299 let end = start.saturating_add(len).min(s.len());
300
301 Ok(Value::String(
302 s.get(start..end).unwrap_or_default().to_string(),
303 ))
304}
305
306fn apply_concat(args: &[Expression], ctx: &HandlerContext) -> Result<Value, ExecutionError> {
307 let mut result = Vec::new();
308
309 for arg in args {
310 let value = evaluate(arg, ctx)?;
311 let bytes = value.as_bytes().ok_or_else(|| {
312 ExecutionError::InvalidParams(format!(
313 "concat expects bytes arguments, got {}",
314 value.type_name()
315 ))
316 })?;
317 result.extend(bytes);
318 }
319
320 Ok(Value::Bytes(result))
321}
322
323pub fn evaluate_expr_value(
336 value: &ExprValue,
337 ctx: &HandlerContext,
338) -> Result<serde_json::Value, ExecutionError> {
339 match value {
340 ExprValue::Literal(lit) => Ok(literal_to_json(lit)),
341 ExprValue::Expr(expr) => {
342 let result = evaluate(expr, ctx)?;
343 Ok(value_to_json(&result))
344 }
345 ExprValue::Interpolated(parts) => {
346 let mut result = String::new();
347 for part in parts {
348 match part {
349 StringPart::Literal(s) => result.push_str(s),
350 StringPart::Expr(expr) => {
351 let val = evaluate(expr, ctx)?;
352 result.push_str(&val.to_string());
353 }
354 }
355 }
356 Ok(serde_json::Value::String(result))
357 }
358 ExprValue::Object(map) => {
359 let mut result = serde_json::Map::new();
360 for (k, v) in map {
361 result.insert(k.clone(), evaluate_expr_value(v, ctx)?);
362 }
363 Ok(serde_json::Value::Object(result))
364 }
365 ExprValue::Array(arr) => {
366 let result: Result<Vec<_>, _> =
367 arr.iter().map(|v| evaluate_expr_value(v, ctx)).collect();
368 Ok(serde_json::Value::Array(result?))
369 }
370 }
371}
372
373fn literal_to_json(lit: &Literal) -> serde_json::Value {
375 match lit {
376 Literal::String(s) => serde_json::Value::String(s.clone()),
377 Literal::Integer(i) => serde_json::Value::Number((*i).into()),
378 Literal::Float(f) => serde_json::Number::from_f64(*f)
379 .map_or(serde_json::Value::Null, serde_json::Value::Number),
380 Literal::Boolean(b) => serde_json::Value::Bool(*b),
381 Literal::Null => serde_json::Value::Null,
382 }
383}
384
385pub fn evaluate_expr_value_to_string(
389 value: &ExprValue,
390 ctx: &HandlerContext,
391) -> Result<String, ExecutionError> {
392 match value {
393 ExprValue::Literal(Literal::String(s)) => Ok(s.clone()),
394 ExprValue::Literal(lit) => {
395 let json = literal_to_json(lit);
397 Ok(match json {
398 serde_json::Value::String(s) => s,
399 serde_json::Value::Number(n) => n.to_string(),
400 serde_json::Value::Bool(b) => b.to_string(),
401 serde_json::Value::Null => "null".to_string(),
402 _ => json.to_string(),
403 })
404 }
405 ExprValue::Expr(expr) => {
406 let result = evaluate(expr, ctx)?;
407 Ok(result.to_string())
408 }
409 ExprValue::Interpolated(parts) => {
410 let mut result = String::new();
411 for part in parts {
412 match part {
413 StringPart::Literal(s) => result.push_str(s),
414 StringPart::Expr(expr) => {
415 let val = evaluate(expr, ctx)?;
416 result.push_str(&val.to_string());
417 }
418 }
419 }
420 Ok(result)
421 }
422 ExprValue::Object(_) | ExprValue::Array(_) => {
423 let json = evaluate_expr_value(value, ctx)?;
425 Ok(json.to_string())
426 }
427 }
428}
429
430pub fn value_to_json(value: &Value) -> serde_json::Value {
432 match value {
433 Value::Bytes(b) => {
434 serde_json::Value::String(Base64::encode_string(b))
436 }
437 Value::String(s) => serde_json::Value::String(s.clone()),
438 Value::Integer(i) => serde_json::Value::Number((*i).into()),
439 Value::Boolean(b) => serde_json::Value::Bool(*b),
440 Value::Null => serde_json::Value::Null,
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::actions::ArtifactValue;
448 use rite_model::{MaterialId, RoleId};
449 use std::collections::HashMap;
450
451 fn empty_context() -> HandlerContext<'static> {
452 static EMPTY_PARAMS: std::sync::LazyLock<HashMap<ParamId, serde_json::Value>> =
453 std::sync::LazyLock::new(HashMap::new);
454 static EMPTY_ARTIFACTS: std::sync::LazyLock<HashMap<ArtifactId, ArtifactValue>> =
455 std::sync::LazyLock::new(HashMap::new);
456 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
457 std::sync::LazyLock::new(HashMap::new);
458 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
459 std::sync::LazyLock::new(HashMap::new);
460 HandlerContext {
461 params: &EMPTY_PARAMS,
462 artifacts: &EMPTY_ARTIFACTS,
463 roles: &EMPTY_ROLES,
464 materials: &EMPTY_MATERIALS,
465 }
466 }
467
468 fn make_context(
471 params: HashMap<ParamId, serde_json::Value>,
472 artifacts: HashMap<ArtifactId, ArtifactValue>,
473 ) -> HandlerContext<'static> {
474 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
475 std::sync::LazyLock::new(HashMap::new);
476 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
477 std::sync::LazyLock::new(HashMap::new);
478 let params_box = Box::leak(Box::new(params));
479 let artifacts_box = Box::leak(Box::new(artifacts));
480 HandlerContext {
481 params: params_box,
482 artifacts: artifacts_box,
483 roles: &EMPTY_ROLES,
484 materials: &EMPTY_MATERIALS,
485 }
486 }
487
488 #[test]
489 fn test_sha256_of_string() {
490 let input = Value::String("hello".to_string());
491 let result = apply_sha256(&input).unwrap();
492
493 if let Value::Bytes(bytes) = result {
494 assert_eq!(bytes.len(), 32); let hex = hex_encode(&bytes);
496 assert_eq!(
497 hex,
498 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
499 );
500 } else {
501 panic!("Expected Bytes");
502 }
503 }
504
505 #[test]
506 fn test_sha256_of_bytes() {
507 let input = Value::Bytes(b"hello".to_vec());
508 let result = apply_sha256(&input).unwrap();
509
510 if let Value::Bytes(bytes) = result {
511 assert_eq!(bytes.len(), 32);
512 } else {
513 panic!("Expected Bytes");
514 }
515 }
516
517 #[test]
518 fn test_hex_encoding() {
519 let input = Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]);
520 let result = apply_hex(&input).unwrap();
521
522 assert_eq!(result, Value::String("deadbeef".to_string()));
523 }
524
525 #[test]
526 fn test_base32_encoding() {
527 let input = Value::Bytes(b"hello".to_vec());
528 let result = apply_base32(&input).unwrap();
529
530 assert_eq!(result, Value::String("NBSWY3DP".to_string()));
532 }
533
534 #[test]
535 fn test_base64_encoding() {
536 let input = Value::Bytes(b"hello".to_vec());
537 let result = apply_base64(&input).unwrap();
538
539 assert_eq!(result, Value::String("aGVsbG8=".to_string()));
540 }
541
542 #[test]
543 fn test_upper() {
544 let input = Value::String("hello".to_string());
545 let result = apply_upper(&input).unwrap();
546
547 assert_eq!(result, Value::String("HELLO".to_string()));
548 }
549
550 #[test]
551 fn test_lower() {
552 let input = Value::String("HELLO".to_string());
553 let result = apply_lower(&input).unwrap();
554
555 assert_eq!(result, Value::String("hello".to_string()));
556 }
557
558 #[test]
559 fn test_pipeline_sha256_hex() {
560 let ctx = empty_context();
561
562 let expr = Expression::Pipeline {
564 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
565 stages: vec![PipeStage::new("sha256"), PipeStage::new("hex")],
566 };
567
568 let result = evaluate(&expr, &ctx).unwrap();
569
570 if let Value::String(s) = result {
571 assert_eq!(
572 s,
573 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
574 );
575 } else {
576 panic!("Expected String");
577 }
578 }
579
580 #[test]
581 fn test_pipeline_sha256_hex_upper() {
582 let ctx = empty_context();
583
584 let expr = Expression::Pipeline {
586 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
587 stages: vec![
588 PipeStage::new("sha256"),
589 PipeStage::new("hex"),
590 PipeStage::new("upper"),
591 ],
592 };
593
594 let result = evaluate(&expr, &ctx).unwrap();
595
596 if let Value::String(s) = result {
597 assert_eq!(
598 s,
599 "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
600 );
601 } else {
602 panic!("Expected String");
603 }
604 }
605
606 #[test]
607 fn test_type_error_sha256_on_integer() {
608 let input = Value::Integer(42);
609 let result = apply_sha256(&input);
610
611 assert!(result.is_err());
612 }
613
614 #[test]
615 fn test_type_error_upper_on_bytes() {
616 let input = Value::Bytes(vec![1, 2, 3]);
617 let result = apply_upper(&input);
618
619 assert!(result.is_err());
620 }
621
622 #[test]
623 fn test_substr_via_pipeline() {
624 let ctx = empty_context();
625
626 let expr = Expression::Pipeline {
628 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
629 stages: vec![PipeStage {
630 function: "substr".to_string(),
631 args: vec![
632 Expression::Literal(Literal::Integer(0)),
633 Expression::Literal(Literal::Integer(4)),
634 ],
635 }],
636 };
637
638 let result = evaluate(&expr, &ctx).unwrap();
639 assert_eq!(result, Value::String("abcd".to_string()));
640 }
641
642 #[test]
643 fn test_substr_from_middle_via_pipeline() {
644 let ctx = empty_context();
645
646 let expr = Expression::Pipeline {
648 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
649 stages: vec![PipeStage {
650 function: "substr".to_string(),
651 args: vec![
652 Expression::Literal(Literal::Integer(2)),
653 Expression::Literal(Literal::Integer(4)),
654 ],
655 }],
656 };
657
658 let result = evaluate(&expr, &ctx).unwrap();
659 assert_eq!(result, Value::String("cdef".to_string()));
660 }
661
662 #[test]
663 fn test_artifact_evaluation() {
664 let mut params = HashMap::new();
665 params.insert(ParamId::new("name"), serde_json::json!("test"));
666
667 let mut artifacts = HashMap::new();
668 artifacts.insert(
670 ArtifactId::new("ksr"),
671 ArtifactValue::Bytes(b"KSR content".to_vec()),
672 );
673
674 let ctx = make_context(params, artifacts);
675
676 let expr =
678 rite_model::expression::parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
679 let result = evaluate(&expr, &ctx).unwrap();
680
681 if let Value::String(s) = result {
682 assert_eq!(s.len(), 64); } else {
684 panic!("Expected String");
685 }
686 }
687
688 #[test]
689 fn test_param_evaluation() {
690 let mut params = HashMap::new();
691 params.insert(ParamId::new("expected_hash"), serde_json::json!("abc123"));
692
693 let artifacts = HashMap::new();
694 let ctx = make_context(params, artifacts);
695
696 let expr = rite_model::expression::parse_expression("${param.expected_hash}").unwrap();
697 let result = evaluate(&expr, &ctx).unwrap();
698
699 assert_eq!(result, Value::String("abc123".to_string()));
700 }
701
702 #[test]
703 fn test_sha384() {
704 let input = Value::String("hello".to_string());
705 let result = apply_sha384(&input).unwrap();
706
707 if let Value::Bytes(bytes) = result {
708 assert_eq!(bytes.len(), 48); } else {
710 panic!("Expected Bytes");
711 }
712 }
713
714 #[test]
715 fn test_sha512() {
716 let input = Value::String("hello".to_string());
717 let result = apply_sha512(&input).unwrap();
718
719 if let Value::Bytes(bytes) = result {
720 assert_eq!(bytes.len(), 64); } else {
722 panic!("Expected Bytes");
723 }
724 }
725
726 #[test]
727 fn test_concat_through_pipeline() {
728 let ctx = empty_context();
729
730 let expr = Expression::Pipeline {
733 source: Box::new(Expression::Literal(Literal::String("ignored".to_string()))),
734 stages: vec![PipeStage {
735 function: "concat".to_string(),
736 args: vec![
737 Expression::Literal(Literal::String("hello".to_string())),
738 Expression::Literal(Literal::String(" ".to_string())),
739 Expression::Literal(Literal::String("world".to_string())),
740 ],
741 }],
742 };
743
744 let result = evaluate(&expr, &ctx).unwrap();
745
746 if let Value::Bytes(b) = result {
748 assert_eq!(b, b"hello world".to_vec());
749 } else {
750 panic!("Expected Bytes, got {result:?}");
751 }
752 }
753
754 #[test]
755 fn test_parse_and_evaluate_pipeline() {
756 let ctx = empty_context();
757
758 let expr = rite_model::expression::parse_expression("${\"test\" | sha256 | hex}").unwrap();
760 let result = evaluate(&expr, &ctx).unwrap();
761
762 if let Value::String(s) = result {
763 assert_eq!(
765 s,
766 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
767 );
768 } else {
769 panic!("Expected String");
770 }
771 }
772
773 mod ceremony_examples {
774 use super::*;
775
776 fn ceremony_context() -> HandlerContext<'static> {
777 let params: HashMap<ParamId, serde_json::Value> = [
778 ("ceremony_id", serde_json::json!("KC-2024-Q4")),
779 ("hsm_serial", serde_json::json!("HSM-001")),
780 ("key_label", serde_json::json!("MASTER-KEY")),
781 ("expected_ksr_hash", serde_json::json!("a3b8f4e91c2d6078")),
782 ("zone_name", serde_json::json!(".")),
783 ("signing_validity_days", serde_json::json!(21)),
784 ]
785 .into_iter()
786 .map(|(k, v)| (ParamId::new(k), v))
787 .collect();
788
789 let mut artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
790 artifacts.insert(
791 ArtifactId::new("ksr"),
792 ArtifactValue::Bytes(b"KSR test content".to_vec()),
793 );
794 artifacts.insert(
795 ArtifactId::new("master_public_key"),
796 ArtifactValue::Bytes(b"PUBLIC KEY DATA".to_vec()),
797 );
798
799 make_context(params, artifacts)
800 }
801
802 #[test]
804 fn dnssec_hash_verification_full_pipeline() {
805 let ctx = ceremony_context();
806
807 let expr = rite_model::expression::parse_expression(
809 "${artifact.ksr | sha256 | hex | substr(0, 16)}",
810 )
811 .unwrap();
812 let result = evaluate(&expr, &ctx).unwrap();
813
814 if let Value::String(s) = result {
815 assert_eq!(s.len(), 16); assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
817 } else {
818 panic!("Expected String");
819 }
820 }
821
822 #[test]
823 fn dnssec_param_comparison() {
824 let ctx = ceremony_context();
825
826 let expr =
828 rite_model::expression::parse_expression("${param.expected_ksr_hash}").unwrap();
829 let result = evaluate(&expr, &ctx).unwrap();
830
831 assert_eq!(result, Value::String("a3b8f4e91c2d6078".to_string()));
832 }
833
834 #[test]
835 fn hsm_fingerprint_computation() {
836 let ctx = ceremony_context();
837
838 let expr = rite_model::expression::parse_expression(
840 "${artifact.master_public_key | sha256 | hex}",
841 )
842 .unwrap();
843 let result = evaluate(&expr, &ctx).unwrap();
844
845 if let Value::String(s) = result {
846 assert_eq!(s.len(), 64); } else {
848 panic!("Expected String");
849 }
850 }
851 }
852}