2 local st
= require
"util.stanza";
4 describe("util.stanza", function()
5 describe("#preserialize()", function()
6 it("should work", function()
7 local stanza
= st
.stanza("message", { type = "chat" }):text_tag("body", "Hello");
8 local stanza2
= st
.preserialize(stanza
);
9 assert.is_table(stanza2
, "Preserialized stanza is a table");
10 assert.is_nil(getmetatable(stanza2
), "Preserialized stanza has no metatable");
11 assert.is_string(stanza2
.name
, "Preserialized stanza has a name field");
12 assert.equal(stanza
.name
, stanza2
.name
, "Preserialized stanza has same name as the input stanza");
13 assert.same(stanza
.attr
, stanza2
.attr
, "Preserialized stanza same attr table as input stanza");
14 assert.is_nil(stanza2
.tags
, "Preserialized stanza has no tag list");
15 assert.is_nil(stanza2
.last_add
, "Preserialized stanza has no last_add marker");
16 assert.is_table(stanza2
[1], "Preserialized child element preserved");
17 assert.equal("body", stanza2
[1].name
, "Preserialized child element name preserved");
21 describe("#deserialize()", function()
22 it("should work", function()
23 local stanza
= { name
= "message", attr
= { type = "chat" }, { name
= "body", attr
= { }, "Hello" } };
24 local stanza2
= st
.deserialize(st
.preserialize(stanza
));
26 assert.is_table(stanza2
, "Deserialized stanza is a table");
27 assert.equal(st
.stanza_mt
, getmetatable(stanza2
), "Deserialized stanza has stanza metatable");
28 assert.is_string(stanza2
.name
, "Deserialized stanza has a name field");
29 assert.equal(stanza
.name
, stanza2
.name
, "Deserialized stanza has same name as the input table");
30 assert.same(stanza
.attr
, stanza2
.attr
, "Deserialized stanza same attr table as input table");
31 assert.is_table(stanza2
.tags
, "Deserialized stanza has tag list");
32 assert.is_table(stanza2
[1], "Deserialized child element preserved");
33 assert.equal("body", stanza2
[1].name
, "Deserialized child element name preserved");
37 describe("#stanza()", function()
38 it("should work", function()
39 local s
= st
.stanza("foo", { xmlns
= "myxmlns", a
= "attr-a" });
40 assert.are
.equal(s
.name
, "foo");
41 assert.are
.equal(s
.attr
.xmlns
, "myxmlns");
42 assert.are
.equal(s
.attr
.a
, "attr-a");
44 local s1
= st
.stanza("s1");
45 assert.are
.equal(s1
.name
, "s1");
46 assert.are
.equal(s1
.attr
.xmlns
, nil);
47 assert.are
.equal(#s1
, 0);
48 assert.are
.equal(#s1
.tags
, 0);
51 assert.are
.equal(#s1
.tags
, 1);
52 assert.are
.equal(s1
.tags
[1].name
, "child1");
54 s1
:tag("grandchild1"):up();
55 assert.are
.equal(#s1
.tags
, 1);
56 assert.are
.equal(s1
.tags
[1].name
, "child1");
57 assert.are
.equal(#s1
.tags
[1], 1);
58 assert.are
.equal(s1
.tags
[1][1].name
, "grandchild1");
60 s1
:up():tag("child2");
61 assert.are
.equal(#s1
.tags
, 2, tostring(s1
));
62 assert.are
.equal(s1
.tags
[1].name
, "child1");
63 assert.are
.equal(s1
.tags
[2].name
, "child2");
64 assert.are
.equal(#s1
.tags
[1], 1);
65 assert.are
.equal(s1
.tags
[1][1].name
, "grandchild1");
67 s1
:up():text("Hello world");
68 assert.are
.equal(#s1
.tags
, 2);
69 assert.are
.equal(#s1
, 3);
70 assert.are
.equal(s1
.tags
[1].name
, "child1");
71 assert.are
.equal(s1
.tags
[2].name
, "child2");
72 assert.are
.equal(#s1
.tags
[1], 1);
73 assert.are
.equal(s1
.tags
[1][1].name
, "grandchild1");
75 it("should work with unicode values", function ()
76 local s
= st
.stanza("Объект", { xmlns
= "myxmlns", ["Объект"] = "&" });
77 assert.are
.equal(s
.name
, "Объект");
78 assert.are
.equal(s
.attr
.xmlns
, "myxmlns");
79 assert.are
.equal(s
.attr
["Объект"], "&");
81 it("should allow :text() with nil and empty strings", function ()
82 local s_control
= st
.stanza("foo");
83 assert.same(st
.stanza("foo"):text(), s_control
);
84 assert.same(st
.stanza("foo"):text(nil), s_control
);
85 assert.same(st
.stanza("foo"):text(""), s_control
);
89 describe("#message()", function()
90 it("should work", function()
91 local m
= st
.message();
92 assert.are
.equal(m
.name
, "message");
96 describe("#iq()", function()
97 it("should create an iq stanza", function()
98 local i
= st
.iq({ type = "get", id
= "foo" });
99 assert.are
.equal("iq", i
.name
);
100 assert.are
.equal("foo", i
.attr
.id
);
101 assert.are
.equal("get", i
.attr
.type);
104 it("should reject stanzas with no attributes", function ()
105 assert.has
.error_match(function ()
111 it("should reject stanzas with no id", function ()
112 assert.has
.error_match(function ()
113 st
.iq({ type = "get" });
114 end, "id attribute");
117 it("should reject stanzas with no type", function ()
118 assert.has
.error_match(function ()
119 st
.iq({ id
= "foo" });
120 end, "type attribute");
125 describe("#presence()", function ()
126 it("should work", function()
127 local p
= st
.presence();
128 assert.are
.equal(p
.name
, "presence");
132 describe("#reply()", function()
133 it("should work for <s>", function()
135 local s
= st
.stanza("s", { to
= "touser", from
= "fromuser", id
= "123" })
138 local r
= st
.reply(s
);
139 assert.are
.equal(r
.name
, s
.name
);
140 assert.are
.equal(r
.id
, s
.id
);
141 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
142 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
143 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
146 it("should work for <iq get>", function()
148 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "get" })
151 local r
= st
.reply(s
);
152 assert.are
.equal(r
.name
, s
.name
);
153 assert.are
.equal(r
.id
, s
.id
);
154 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
155 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
156 assert.are
.equal(r
.attr
.type, "result");
157 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
160 it("should work for <iq set>", function()
162 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "set" })
165 local r
= st
.reply(s
);
166 assert.are
.equal(r
.name
, s
.name
);
167 assert.are
.equal(r
.id
, s
.id
);
168 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
169 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
170 assert.are
.equal(r
.attr
.type, "result");
171 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
175 describe("#error_reply()", function()
176 it("should work for <s>", function()
178 local s
= st
.stanza("s", { to
= "touser", from
= "fromuser", id
= "123" })
181 local r
= st
.error_reply(s
, "cancel", "service-unavailable");
182 assert.are
.equal(r
.name
, s
.name
);
183 assert.are
.equal(r
.id
, s
.id
);
184 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
185 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
186 assert.are
.equal(#r
.tags
, 1);
187 assert.are
.equal(r
.tags
[1].tags
[1].name
, "service-unavailable");
190 it("should work for <iq get>", function()
192 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "get" })
195 local r
= st
.error_reply(s
, "cancel", "service-unavailable");
196 assert.are
.equal(r
.name
, s
.name
);
197 assert.are
.equal(r
.id
, s
.id
);
198 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
199 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
200 assert.are
.equal(r
.attr
.type, "error");
201 assert.are
.equal(#r
.tags
, 1);
202 assert.are
.equal(r
.tags
[1].tags
[1].name
, "service-unavailable");
206 describe("should reject #invalid", function ()
207 local invalid_names
= {
208 ["empty string"] = "", ["characters"] = "<>";
210 local invalid_data
= {
211 ["number"] = 1234, ["table"] = {};
212 ["utf8"] = string.char(0xF4, 0x90, 0x80, 0x80);
213 ["nil"] = "nil"; ["boolean"] = true;
216 for value_type
, value
in pairs(invalid_names
) do
217 it(value_type
.." in tag names", function ()
218 assert.error_matches(function ()
222 it(value_type
.." in attribute names", function ()
223 assert.error_matches(function ()
224 st
.stanza("valid", { [value
] = "valid" });
228 for value_type
, value
in pairs(invalid_data
) do
229 if value
== "nil" then value
= nil; end
230 it(value_type
.." in tag names", function ()
231 assert.error_matches(function ()
235 it(value_type
.." in attribute names", function ()
236 assert.error_matches(function ()
237 st
.stanza("valid", { [value
] = "valid" });
241 it(value_type
.." in attribute values", function ()
242 assert.error_matches(function ()
243 st
.stanza("valid", { valid
= value
});
246 it(value_type
.." in text node", function ()
247 assert.error_matches(function ()
248 st
.stanza("valid"):text(value
);
255 describe("#is_stanza", function ()
256 -- is_stanza(any) -> boolean
257 it("identifies stanzas as stanzas", function ()
258 assert.truthy(st
.is_stanza(st
.stanza("x")));
260 it("identifies strings as not stanzas", function ()
261 assert.falsy(st
.is_stanza(""));
263 it("identifies numbers as not stanzas", function ()
264 assert.falsy(st
.is_stanza(1));
266 it("identifies tables as not stanzas", function ()
267 assert.falsy(st
.is_stanza({}));
271 describe("#remove_children", function ()
272 it("should work", function ()
273 local s
= st
.stanza("x", {xmlns
="test"})
274 :tag("y", {xmlns
="test"}):up()
275 :tag("z", {xmlns
="test2"}):up()
276 :tag("x", {xmlns
="test2"}):up()
278 s
:remove_children("x");
279 assert.falsy(s
:get_child("x"))
280 assert.truthy(s
:get_child("z","test2"));
281 assert.truthy(s
:get_child("x","test2"));
283 s
:remove_children(nil, "test2");
284 assert.truthy(s
:get_child("y"))
285 assert.falsy(s
:get_child(nil,"test2"));
288 assert.falsy(s
.tags
[1]);
292 describe("#maptags", function ()
293 it("should work", function ()
294 local s
= st
.stanza("test")
300 local function one_filter(tag)
301 if tag.name
== "one" then
306 assert.equal(4, #s
.tags
);
307 s
:maptags(one_filter
);
308 assert.equal(2, #s
.tags
);
311 it("should work with multiple consecutive text nodes", function ()
312 local s
= st
.deserialize({
335 xmlns
= "http://jabber.org/protocol/caps";
336 node
= "http://psi-im.org";
344 to
= "user@example.com/jflsjfld";
345 from
= "room@chat.example.org/nick";
349 assert.equal(4, #s
.tags
);
351 s
:maptags(function (tag) return tag; end);
352 assert.equal(4, #s
.tags
);
354 s
:maptags(function (tag)
355 if tag.name
== "c" then
360 assert.equal(3, #s
.tags
);
362 it("errors on invalid data - #981", function ()
363 local s
= st
.message({}, "Hello");
364 s
.tags
[1] = st
.clone(s
.tags
[1]);
365 assert.has_error_match(function ()
366 s
:maptags(function () end);
367 end, "Invalid stanza");
371 describe("#clone", function ()
372 it("works", function ()
373 local s
= st
.message({type="chat"}, "Hello"):reset();
374 local c
= st
.clone(s
);
378 it("works", function ()
379 assert.has_error(function ()
380 st
.clone("this is not a stanza");
385 describe("top_tag", function ()
386 local xml_parse
= require
"util.xml".parse
;
387 it("works", function ()
388 local s
= st
.message({type="chat"}, "Hello");
389 local top_tag
= s
:top_tag();
390 assert.is_string(top_tag
);
391 assert.not_equal("/>", top_tag
:sub(-2, -1));
392 assert.equal(">", top_tag
:sub(-1, -1));
393 local s2
= xml_parse(top_tag
.."</message>");
394 assert(st
.is_stanza(s2
));
395 assert.equal("message", s2
.name
);
396 assert.equal(0, #s2
);
397 assert.equal(0, #s2
.tags
);
398 assert.equal("chat", s2
.attr
.type);
401 it("works with namespaced attributes", function ()
402 local s
= xml_parse
[[<message foo:bar='true' xmlns:foo='my-awesome-ns'/>]];
403 local top_tag
= s
:top_tag();
404 assert.is_string(top_tag
);
405 assert.not_equal("/>", top_tag
:sub(-2, -1));
406 assert.equal(">", top_tag
:sub(-1, -1));
407 local s2
= xml_parse(top_tag
.."</message>");
408 assert(st
.is_stanza(s2
));
409 assert.equal("message", s2
.name
);
410 assert.equal(0, #s2
);
411 assert.equal(0, #s2
.tags
);
412 assert.equal("true", s2
.attr
["my-awesome-ns\1bar"]);