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({ id
= "foo" });
99 assert.are
.equal("iq", i
.name
);
100 assert.are
.equal("foo", i
.attr
.id
);
103 it("should reject stanzas with no id", function ()
104 assert.has
.error_match(function ()
106 end, "id attribute");
108 assert.has
.error_match(function ()
109 st
.iq({ foo
= "bar" });
110 end, "id attribute");
114 describe("#presence()", function ()
115 it("should work", function()
116 local p
= st
.presence();
117 assert.are
.equal(p
.name
, "presence");
121 describe("#reply()", function()
122 it("should work for <s>", function()
124 local s
= st
.stanza("s", { to
= "touser", from
= "fromuser", id
= "123" })
127 local r
= st
.reply(s
);
128 assert.are
.equal(r
.name
, s
.name
);
129 assert.are
.equal(r
.id
, s
.id
);
130 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
131 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
132 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
135 it("should work for <iq get>", function()
137 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "get" })
140 local r
= st
.reply(s
);
141 assert.are
.equal(r
.name
, s
.name
);
142 assert.are
.equal(r
.id
, s
.id
);
143 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
144 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
145 assert.are
.equal(r
.attr
.type, "result");
146 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
149 it("should work for <iq set>", function()
151 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "set" })
154 local r
= st
.reply(s
);
155 assert.are
.equal(r
.name
, s
.name
);
156 assert.are
.equal(r
.id
, s
.id
);
157 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
158 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
159 assert.are
.equal(r
.attr
.type, "result");
160 assert.are
.equal(#r
.tags
, 0, "A reply should not include children of the original stanza");
164 describe("#error_reply()", function()
165 it("should work for <s>", function()
167 local s
= st
.stanza("s", { to
= "touser", from
= "fromuser", id
= "123" })
170 local r
= st
.error_reply(s
, "cancel", "service-unavailable");
171 assert.are
.equal(r
.name
, s
.name
);
172 assert.are
.equal(r
.id
, s
.id
);
173 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
174 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
175 assert.are
.equal(#r
.tags
, 1);
176 assert.are
.equal(r
.tags
[1].tags
[1].name
, "service-unavailable");
179 it("should work for <iq get>", function()
181 local s
= st
.stanza("iq", { to
= "touser", from
= "fromuser", id
= "123", type = "get" })
184 local r
= st
.error_reply(s
, "cancel", "service-unavailable");
185 assert.are
.equal(r
.name
, s
.name
);
186 assert.are
.equal(r
.id
, s
.id
);
187 assert.are
.equal(r
.attr
.to
, s
.attr
.from
);
188 assert.are
.equal(r
.attr
.from
, s
.attr
.to
);
189 assert.are
.equal(r
.attr
.type, "error");
190 assert.are
.equal(#r
.tags
, 1);
191 assert.are
.equal(r
.tags
[1].tags
[1].name
, "service-unavailable");
195 describe("should reject #invalid", function ()
196 local invalid_names
= {
197 ["empty string"] = "", ["characters"] = "<>";
199 local invalid_data
= {
200 ["number"] = 1234, ["table"] = {};
201 ["utf8"] = string.char(0xF4, 0x90, 0x80, 0x80);
202 ["nil"] = "nil"; ["boolean"] = true;
205 for value_type
, value
in pairs(invalid_names
) do
206 it(value_type
.." in tag names", function ()
207 assert.error_matches(function ()
211 it(value_type
.." in attribute names", function ()
212 assert.error_matches(function ()
213 st
.stanza("valid", { [value
] = "valid" });
217 for value_type
, value
in pairs(invalid_data
) do
218 if value
== "nil" then value
= nil; end
219 it(value_type
.." in tag names", function ()
220 assert.error_matches(function ()
224 it(value_type
.." in attribute names", function ()
225 assert.error_matches(function ()
226 st
.stanza("valid", { [value
] = "valid" });
230 it(value_type
.." in attribute values", function ()
231 assert.error_matches(function ()
232 st
.stanza("valid", { valid
= value
});
235 it(value_type
.." in text node", function ()
236 assert.error_matches(function ()
237 st
.stanza("valid"):text(value
);
244 describe("#is_stanza", function ()
245 -- is_stanza(any) -> boolean
246 it("identifies stanzas as stanzas", function ()
247 assert.truthy(st
.is_stanza(st
.stanza("x")));
249 it("identifies strings as not stanzas", function ()
250 assert.falsy(st
.is_stanza(""));
252 it("identifies numbers as not stanzas", function ()
253 assert.falsy(st
.is_stanza(1));
255 it("identifies tables as not stanzas", function ()
256 assert.falsy(st
.is_stanza({}));
260 describe("#remove_children", function ()
261 it("should work", function ()
262 local s
= st
.stanza("x", {xmlns
="test"})
263 :tag("y", {xmlns
="test"}):up()
264 :tag("z", {xmlns
="test2"}):up()
265 :tag("x", {xmlns
="test2"}):up()
267 s
:remove_children("x");
268 assert.falsy(s
:get_child("x"))
269 assert.truthy(s
:get_child("z","test2"));
270 assert.truthy(s
:get_child("x","test2"));
272 s
:remove_children(nil, "test2");
273 assert.truthy(s
:get_child("y"))
274 assert.falsy(s
:get_child(nil,"test2"));
277 assert.falsy(s
.tags
[1]);
281 describe("#maptags", function ()
282 it("should work", function ()
283 local s
= st
.stanza("test")
289 local function one_filter(tag)
290 if tag.name
== "one" then
295 assert.equal(4, #s
.tags
);
296 s
:maptags(one_filter
);
297 assert.equal(2, #s
.tags
);
300 it("should work with multiple consecutive text nodes", function ()
301 local s
= st
.deserialize({
324 xmlns
= "http://jabber.org/protocol/caps";
325 node
= "http://psi-im.org";
333 to
= "user@example.com/jflsjfld";
334 from
= "room@chat.example.org/nick";
338 assert.equal(4, #s
.tags
);
340 s
:maptags(function (tag) return tag; end);
341 assert.equal(4, #s
.tags
);
343 s
:maptags(function (tag)
344 if tag.name
== "c" then
349 assert.equal(3, #s
.tags
);
351 it("errors on invalid data - #981", function ()
352 local s
= st
.message({}, "Hello");
353 s
.tags
[1] = st
.clone(s
.tags
[1]);
354 assert.has_error_match(function ()
355 s
:maptags(function () end);
356 end, "Invalid stanza");
360 describe("#clone", function ()
361 it("works", function ()
362 local s
= st
.message({type="chat"}, "Hello"):reset();
363 local c
= st
.clone(s
);
367 it("works", function ()
368 assert.has_error(function ()
369 st
.clone("this is not a stanza");