assay-lua 0.20.10

General-purpose enhanced Lua runtime. Batteries-included scripting, automation, and web services.
Documentation
mod common;

use common::{eval_lua, run_lua};

#[tokio::test]
async fn test_yaml_parse_object() {
    let script = r#"
        local data = yaml.parse("name: assay\nversion: 1\n")
        assert.eq(data.name, "assay")
        assert.eq(data.version, 1)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_array() {
    let script = r#"
        local data = yaml.parse("- one\n- two\n- three\n")
        assert.eq(#data, 3)
        assert.eq(data[1], "one")
        assert.eq(data[3], "three")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_all_documents() {
    let script = r#"
        local docs = yaml.parse_all("kind: Service\nmetadata:\n  name: api\n---\nkind: Deployment\nmetadata:\n  name: worker\n")
        assert.eq(#docs, 2)
        assert.eq(docs[1].kind, "Service")
        assert.eq(docs[1].metadata.name, "api")
        assert.eq(docs[2].kind, "Deployment")
        assert.eq(docs[2].metadata.name, "worker")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_all_skips_empty_documents() {
    let script = r#"
        local docs = yaml.parse_all("---\nkind: ConfigMap\n---\n---\nkind: Secret\n")
        assert.eq(#docs, 2)
        assert.eq(docs[1].kind, "ConfigMap")
        assert.eq(docs[2].kind, "Secret")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_nested() {
    let script = r#"
        local data = yaml.parse("server:\n  host: localhost\n  port: 8080\n")
        assert.eq(data.server.host, "localhost")
        assert.eq(data.server.port, 8080)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_table() {
    let script = r#"
        local encoded = yaml.encode({name = "assay", version = 1})
        assert.contains(encoded, "name")
        assert.contains(encoded, "assay")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_roundtrip() {
    let script = r#"
        local input = "greeting: hello\ncount: 42\n"
        local data = yaml.parse(input)
        assert.eq(data.greeting, "hello")
        assert.eq(data.count, 42)
        local encoded = yaml.encode(data)
        local data2 = yaml.parse(encoded)
        assert.eq(data2.greeting, "hello")
        assert.eq(data2.count, 42)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_error() {
    let result = run_lua(r#"yaml.parse(":\n  :\n  - ][")"#).await;
    assert!(result.is_err());
}

#[tokio::test]
async fn test_yaml_parse_multiline_string() {
    let script = r#"
        local data = yaml.parse("text: |\n  line one\n  line two\n")
        assert.contains(data.text, "line one")
        assert.contains(data.text, "line two")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_boolean_and_null() {
    let script = r#"
        local data = yaml.parse("enabled: true\ndisabled: false\nempty: null\n")
        assert.eq(data.enabled, true)
        assert.eq(data.disabled, false)
        assert.eq(data.empty, nil)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_then_parse_preserves_types() {
    let result: bool = eval_lua(
        r#"
        local t = {name = "test", count = 10, active = true}
        local encoded = yaml.encode(t)
        local decoded = yaml.parse(encoded)
        return decoded.count == 10 and decoded.active == true and decoded.name == "test"
    "#,
    )
    .await;
    assert!(result);
}

#[tokio::test]
async fn test_yaml_parse_local_tag_on_scalar() {
    let script = r#"
        local data = yaml.parse("a: !Ref some-value\n")
        assert.eq(data.a["!Ref"], "some-value")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_local_tag_on_sequence() {
    let script = r#"
        local data = yaml.parse(".anchor:\n  script:\n    - echo hi\njob:\n  script:\n    - !reference [.anchor, script]\n")
        assert.eq(data[".anchor"].script[1], "echo hi")
        local ref = data.job.script[1]["!reference"]
        assert.eq(#ref, 2)
        assert.eq(ref[1], ".anchor")
        assert.eq(ref[2], "script")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_local_tag_on_mapping() {
    let script = r#"
        local data = yaml.parse("b: !custom {x: 1, y: two}\n")
        assert.eq(data.b["!custom"].x, 1)
        assert.eq(data.b["!custom"].y, "two")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_standard_tags_resolve_to_scalars() {
    let script = r#"
        local data = yaml.parse("s: !!str 1\ni: !!int \"5\"\nf: !!float \"1.5\"\nb: !!bool \"true\"\nn: !!null \"~\"\nt: !!timestamp 2001-12-14\nlong: !<tag:yaml.org,2002:str> 1\n")
        assert.eq(type(data.s), "string")
        assert.eq(data.s, "1")
        assert.eq(data.i, 5)
        assert.eq(data.f, 1.5)
        assert.eq(data.b, true)
        assert.eq(data.n, nil)
        assert.eq(data.t, "2001-12-14")
        assert.eq(data.long, "1")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_tag_inside_sequence() {
    let script = r#"
        local data = yaml.parse("items:\n  - !ref one\n  - plain\n  - !custom {k: v}\n")
        assert.eq(#data.items, 3)
        assert.eq(data.items[1]["!ref"], "one")
        assert.eq(data.items[2], "plain")
        assert.eq(data.items[3]["!custom"].k, "v")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_tags_strip() {
    let script = r#"
        local data = yaml.parse("a: !custom {x: 1}\nb: !reference [.anchor, script]\nc: !Ref scalar\n", { tags = "strip" })
        assert.eq(data.a.x, 1)
        assert.eq(data.b[1], ".anchor")
        assert.eq(data.b[2], "script")
        assert.eq(data.c, "scalar")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_tags_wrap_is_the_default() {
    let script = r#"
        local implicit = yaml.parse("a: !custom {x: 1}\n")
        local explicit = yaml.parse("a: !custom {x: 1}\n", { tags = "wrap" })
        assert.eq(implicit.a["!custom"].x, 1)
        assert.eq(explicit.a["!custom"].x, 1)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_rejects_unknown_tags_option() {
    let script = r#"
        local ok, err = pcall(yaml.parse, "a: 1\n", { tags = "drop" })
        assert.eq(ok, false)
        assert.contains(tostring(err), "yaml.parse: tags must be \"wrap\" or \"strip\"")
        local ok2, err2 = pcall(yaml.parse_all, "a: 1\n", { tags = 7 })
        assert.eq(ok2, false)
        assert.contains(tostring(err2), "yaml.parse_all: tags must be \"wrap\" or \"strip\"")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_all_with_tagged_document() {
    let script = r#"
        local docs = yaml.parse_all("kind: Pipeline\njob:\n  script:\n    - !reference [.anchor, script]\n---\n---\nkind: Plain\n")
        assert.eq(#docs, 2)
        assert.eq(docs[1].kind, "Pipeline")
        assert.eq(docs[1].job.script[1]["!reference"][2], "script")
        assert.eq(docs[2].kind, "Plain")

        local stripped = yaml.parse_all("a: !custom {x: 1}\n", { tags = "strip" })
        assert.eq(stripped[1].a.x, 1)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_roundtrips_a_wrapped_tag() {
    let script = r#"
        local encoded = yaml.encode({ ["!reference"] = { ".anchor", "script" } }, { tags = "wrap" })
        assert.eq(encoded, "!reference\n- '.anchor'\n- script\n")
        local back = yaml.parse(encoded)
        assert.eq(back["!reference"][1], ".anchor")
        assert.eq(back["!reference"][2], "script")

        local nested = yaml.parse("job:\n  script:\n    - !reference [.anchor, script]\n")
        local again = yaml.parse(yaml.encode(nested, { tags = "wrap" }))
        assert.eq(again.job.script[1]["!reference"][1], ".anchor")
        assert.eq(again.job.script[1]["!reference"][2], "script")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_leaves_bang_keys_alone_by_default() {
    let script = r#"
        assert.eq(yaml.encode({ ["!weird"] = 1 }), "'!weird': 1\n")
        assert.eq(yaml.encode({ ["!weird"] = 1 }, { tags = "strip" }), "'!weird': 1\n")
        assert.eq(yaml.encode({ ["!weird"] = 1 }, { tags = "wrap" }), "!weird 1\n")
        assert.eq(yaml.encode({ ["!weird"] = 1, other = 2 }), "'!weird': 1\nother: 2\n")
        assert.eq(yaml.encode({ ["!weird"] = 1, other = 2 }, { tags = "wrap" }), "'!weird': 1\nother: 2\n")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_rejects_unknown_tags_option() {
    let script = r#"
        local ok, err = pcall(yaml.encode, { a = 1 }, { tags = "keep" })
        assert.eq(ok, false)
        assert.contains(tostring(err), "yaml.encode: tags must be \"wrap\" or \"strip\"")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_tag_without_a_payload() {
    let script = r#"
        local data = yaml.parse("a: !foo\nb: !bar null\n")
        assert.eq(data.a["!foo"], "")
        assert.eq(data.b["!bar"], "")

        local again = yaml.parse(yaml.encode(data, { tags = "wrap" }))
        assert.eq(again.a["!foo"], "")
        assert.eq(again.b["!bar"], "")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_keeps_non_string_mapping_keys_as_written() {
    let script = r#"
        local data = yaml.parse("9000: a\n-3: b\n+5: c\n0x1f: d\n1.5: e\n1.50: f\n1e3: g\n")
        assert.eq(data["9000"], "a")
        assert.eq(data["-3"], "b")
        assert.eq(data["+5"], "c")
        assert.eq(data["0x1f"], "d")
        assert.eq(data["1.5"], "e")
        assert.eq(data["1.50"], "f")
        assert.eq(data["1e3"], "g")

        local flags = yaml.parse("true: a\nTRUE: b\nFalse: c\nnull: d\n~: e\nNull: f\n")
        assert.eq(flags["true"], "a")
        assert.eq(flags["TRUE"], "b")
        assert.eq(flags["False"], "c")
        assert.eq(flags["null"], "d")
        assert.eq(flags["~"], "e")
        assert.eq(flags["Null"], "f")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_duplicate_keys_keep_the_last() {
    let script = r#"
        assert.eq(yaml.parse("a: 1\na: 2\n").a, 2)
        assert.eq(yaml.parse("a: 1\na: 2\na: 3\n").a, 3)
        assert.eq(yaml.parse("a: 1\na: null\n").a, nil)
        assert.eq(yaml.parse("m:\n  k: 1\n  k: 2\n").m.k, 2)

        local docs = yaml.parse_all("a: 1\na: 2\n---\nb: 1\n")
        assert.eq(docs[1].a, 2)
        assert.eq(docs[2].b, 1)
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_encode_untagged_keeps_sorted_keys() {
    let script = r#"
        assert.eq(yaml.encode({ z = 1, a = 2, m = { b = 1, a = 2 } }), "a: 2\nm:\n  a: 2\n  b: 1\nz: 1\n")
        assert.eq(yaml.encode({ "one", "two" }), "- one\n- two\n")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_untagged_document_is_unchanged() {
    let script = r#"
        local data = yaml.parse("name: assay\nversion: 1\nratio: 1.5\nenabled: true\nempty: null\nlist:\n  - one\n  - two\nnested:\n  host: localhost\n  port: 8080\ntext: |\n  line one\n  line two\n")
        assert.eq(data.name, "assay")
        assert.eq(data.version, 1)
        assert.eq(data.ratio, 1.5)
        assert.eq(data.enabled, true)
        assert.eq(data.empty, nil)
        assert.eq(#data.list, 2)
        assert.eq(data.list[1], "one")
        assert.eq(data.nested.host, "localhost")
        assert.eq(data.nested.port, 8080)
        assert.contains(data.text, "line one")
        assert.contains(data.text, "line two")
    "#;
    run_lua(script).await.unwrap();
}

#[tokio::test]
async fn test_yaml_parse_untagged_anchors_and_merge_keys_are_unchanged() {
    let script = r#"
        local data = yaml.parse("base: &b\n  x: 1\n  y: two\nuse: *b\n")
        assert.eq(data.use.x, 1)
        assert.eq(data.use.y, "two")

        local merged = yaml.parse("base: &b\n  x: 1\n  y: 2\nderived:\n  <<: *b\n  y: 3\n")
        assert.eq(merged.derived["<<"].x, 1)
        assert.eq(merged.derived.y, 3)

        local specials = yaml.parse("a: .inf\nb: .nan\nc: 18446744073709551615\n")
        assert.eq(specials.a, nil)
        assert.eq(specials.b, nil)
        assert.eq(type(specials.c), "number")
    "#;
    run_lua(script).await.unwrap();
}