local st = require "util.stanza"; describe("util.stanza", function() describe("#preserialize()", function() it("should work", function() local stanza = st.stanza("message", { type = "chat" }):text_tag("body", "Hello"); local stanza2 = st.preserialize(stanza); assert.is_table(stanza2, "Preserialized stanza is a table"); assert.is_nil(getmetatable(stanza2), "Preserialized stanza has no metatable"); assert.is_string(stanza2.name, "Preserialized stanza has a name field"); assert.equal(stanza.name, stanza2.name, "Preserialized stanza has same name as the input stanza"); assert.same(stanza.attr, stanza2.attr, "Preserialized stanza same attr table as input stanza"); assert.is_nil(stanza2.tags, "Preserialized stanza has no tag list"); assert.is_nil(stanza2.last_add, "Preserialized stanza has no last_add marker"); assert.is_table(stanza2[1], "Preserialized child element preserved"); assert.equal("body", stanza2[1].name, "Preserialized child element name preserved"); end); end); describe("#deserialize()", function() it("should work", function() local stanza = { name = "message", attr = { type = "chat" }, { name = "body", attr = { }, "Hello" } }; local stanza2 = st.deserialize(st.preserialize(stanza)); assert.is_table(stanza2, "Deserialized stanza is a table"); assert.equal(st.stanza_mt, getmetatable(stanza2), "Deserialized stanza has stanza metatable"); assert.is_string(stanza2.name, "Deserialized stanza has a name field"); assert.equal(stanza.name, stanza2.name, "Deserialized stanza has same name as the input table"); assert.same(stanza.attr, stanza2.attr, "Deserialized stanza same attr table as input table"); assert.is_table(stanza2.tags, "Deserialized stanza has tag list"); assert.is_table(stanza2[1], "Deserialized child element preserved"); assert.equal("body", stanza2[1].name, "Deserialized child element name preserved"); end); end); describe("#stanza()", function() it("should work", function() local s = st.stanza("foo", { xmlns = "myxmlns", a = "attr-a" }); assert.are.equal(s.name, "foo"); assert.are.equal(s.attr.xmlns, "myxmlns"); assert.are.equal(s.attr.a, "attr-a"); local s1 = st.stanza("s1"); assert.are.equal(s1.name, "s1"); assert.are.equal(s1.attr.xmlns, nil); assert.are.equal(#s1, 0); assert.are.equal(#s1.tags, 0); s1:tag("child1"); assert.are.equal(#s1.tags, 1); assert.are.equal(s1.tags[1].name, "child1"); s1:tag("grandchild1"):up(); assert.are.equal(#s1.tags, 1); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); s1:up():tag("child2"); assert.are.equal(#s1.tags, 2, tostring(s1)); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(s1.tags[2].name, "child2"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); s1:up():text("Hello world"); assert.are.equal(#s1.tags, 2); assert.are.equal(#s1, 3); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(s1.tags[2].name, "child2"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); end); it("should work with unicode values", function () local s = st.stanza("Объект", { xmlns = "myxmlns", ["Объект"] = "&" }); assert.are.equal(s.name, "Объект"); assert.are.equal(s.attr.xmlns, "myxmlns"); assert.are.equal(s.attr["Объект"], "&"); end); it("should allow :text() with nil and empty strings", function () local s_control = st.stanza("foo"); assert.same(st.stanza("foo"):text(), s_control); assert.same(st.stanza("foo"):text(nil), s_control); assert.same(st.stanza("foo"):text(""), s_control); end); end); describe("#message()", function() it("should work", function() local m = st.message(); assert.are.equal(m.name, "message"); end); end); describe("#iq()", function() it("should create an iq stanza", function() local i = st.iq({ id = "foo" }); assert.are.equal("iq", i.name); assert.are.equal("foo", i.attr.id); end); it("should reject stanzas with no id", function () assert.has.error_match(function () st.iq(); end, "id attribute"); assert.has.error_match(function () st.iq({ foo = "bar" }); end, "id attribute"); end); end); describe("#presence()", function () it("should work", function() local p = st.presence(); assert.are.equal(p.name, "presence"); end); end); describe("#reply()", function() it("should work for ", function() -- Test stanza local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "result"); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "set" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "result"); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); end); describe("#error_reply()", function() it("should work for ", function() -- Test stanza local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" }) :tag("child1"); -- Make reply stanza local r = st.error_reply(s, "cancel", "service-unavailable"); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" }) :tag("child1"); -- Make reply stanza local r = st.error_reply(s, "cancel", "service-unavailable"); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "error"); assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); end); end); describe("should reject #invalid", function () local invalid_names = { ["empty string"] = "", ["characters"] = "<>"; } local invalid_data = { ["number"] = 1234, ["table"] = {}; ["utf8"] = string.char(0xF4, 0x90, 0x80, 0x80); ["nil"] = "nil"; ["boolean"] = true; ["control characters"] = "\0\1\2\3"; }; for value_type, value in pairs(invalid_names) do it(value_type.." in tag names", function () assert.error_matches(function () st.stanza(value); end, value_type); end); it(value_type.." in attribute names", function () assert.error_matches(function () st.stanza("valid", { [value] = "valid" }); end, value_type); end); end for value_type, value in pairs(invalid_data) do if value == "nil" then value = nil; end it(value_type.." in tag names", function () assert.error_matches(function () st.stanza(value); end, value_type); end); it(value_type.." in attribute names", function () assert.error_matches(function () st.stanza("valid", { [value] = "valid" }); end, value_type); end); if value ~= nil then it(value_type.." in attribute values", function () assert.error_matches(function () st.stanza("valid", { valid = value }); end, value_type); end); it(value_type.." in text node", function () assert.error_matches(function () st.stanza("valid"):text(value); end, value_type); end); end end end); describe("#is_stanza", function () -- is_stanza(any) -> boolean it("identifies stanzas as stanzas", function () assert.truthy(st.is_stanza(st.stanza("x"))); end); it("identifies strings as not stanzas", function () assert.falsy(st.is_stanza("")); end); it("identifies numbers as not stanzas", function () assert.falsy(st.is_stanza(1)); end); it("identifies tables as not stanzas", function () assert.falsy(st.is_stanza({})); end); end); describe("#remove_children", function () it("should work", function () local s = st.stanza("x", {xmlns="test"}) :tag("y", {xmlns="test"}):up() :tag("z", {xmlns="test2"}):up() :tag("x", {xmlns="test2"}):up() s:remove_children("x"); assert.falsy(s:get_child("x")) assert.truthy(s:get_child("z","test2")); assert.truthy(s:get_child("x","test2")); s:remove_children(nil, "test2"); assert.truthy(s:get_child("y")) assert.falsy(s:get_child(nil,"test2")); s:remove_children(); assert.falsy(s.tags[1]); end); end); describe("#maptags", function () it("should work", function () local s = st.stanza("test") :tag("one"):up() :tag("two"):up() :tag("one"):up() :tag("three"):up(); local function one_filter(tag) if tag.name == "one" then return nil; end return tag; end assert.equal(4, #s.tags); s:maptags(one_filter); assert.equal(2, #s.tags); end); it("should work with multiple consecutive text nodes", function () local s = st.deserialize({ "\n"; { "away"; name = "show"; attr = {}; }; "\n"; { "I am away"; name = "status"; attr = {}; }; "\n"; { "0"; name = "priority"; attr = {}; }; "\n"; { name = "c"; attr = { xmlns = "http://jabber.org/protocol/caps"; node = "http://psi-im.org"; hash = "sha-1"; }; }; "\n"; "\n"; name = "presence"; attr = { to = "user@example.com/jflsjfld"; from = "room@chat.example.org/nick"; }; }); assert.equal(4, #s.tags); s:maptags(function (tag) return tag; end); assert.equal(4, #s.tags); s:maptags(function (tag) if tag.name == "c" then return nil; end return tag; end); assert.equal(3, #s.tags); end); it("errors on invalid data - #981", function () local s = st.message({}, "Hello"); s.tags[1] = st.clone(s.tags[1]); assert.has_error_match(function () s:maptags(function () end); end, "Invalid stanza"); end); end); describe("#clone", function () it("works", function () local s = st.message({type="chat"}, "Hello"):reset(); local c = st.clone(s); assert.same(s, c); end); it("works", function () assert.has_error(function () st.clone("this is not a stanza"); end); end); end); end);