mod_admin_telnet: Identify native bidi sessions
[prosody.git] / spec / util_stanza_spec.lua
blob38503ab7590203ed9717c3d7b076dd880c56d081
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");
18 end);
19 end);
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");
34 end);
35 end);
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);
50 s1:tag("child1");
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");
74 end);
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["Объект"], "&");
80 end);
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);
86 end);
87 end);
89 describe("#message()", function()
90 it("should work", function()
91 local m = st.message();
92 assert.are.equal(m.name, "message");
93 end);
94 end);
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);
102 end);
104 it("should reject stanzas with no attributes", function ()
105 assert.has.error_match(function ()
106 st.iq();
107 end, "attributes");
108 end);
111 it("should reject stanzas with no id", function ()
112 assert.has.error_match(function ()
113 st.iq({ type = "get" });
114 end, "id attribute");
115 end);
117 it("should reject stanzas with no type", function ()
118 assert.has.error_match(function ()
119 st.iq({ id = "foo" });
120 end, "type attribute");
122 end);
123 end);
125 describe("#presence()", function ()
126 it("should work", function()
127 local p = st.presence();
128 assert.are.equal(p.name, "presence");
129 end);
130 end);
132 describe("#reply()", function()
133 it("should work for <s>", function()
134 -- Test stanza
135 local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" })
136 :tag("child1");
137 -- Make reply stanza
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");
144 end);
146 it("should work for <iq get>", function()
147 -- Test stanza
148 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
149 :tag("child1");
150 -- Make reply stanza
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");
158 end);
160 it("should work for <iq set>", function()
161 -- Test stanza
162 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "set" })
163 :tag("child1");
164 -- Make reply stanza
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");
172 end);
173 end);
175 describe("#error_reply()", function()
176 it("should work for <s>", function()
177 -- Test stanza
178 local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" })
179 :tag("child1");
180 -- Make reply stanza
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");
188 end);
190 it("should work for <iq get>", function()
191 -- Test stanza
192 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
193 :tag("child1");
194 -- Make reply stanza
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");
203 end);
204 end);
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 ()
219 st.stanza(value);
220 end, value_type);
221 end);
222 it(value_type.." in attribute names", function ()
223 assert.error_matches(function ()
224 st.stanza("valid", { [value] = "valid" });
225 end, value_type);
226 end);
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 ()
232 st.stanza(value);
233 end, value_type);
234 end);
235 it(value_type.." in attribute names", function ()
236 assert.error_matches(function ()
237 st.stanza("valid", { [value] = "valid" });
238 end, value_type);
239 end);
240 if value ~= nil then
241 it(value_type.." in attribute values", function ()
242 assert.error_matches(function ()
243 st.stanza("valid", { valid = value });
244 end, value_type);
245 end);
246 it(value_type.." in text node", function ()
247 assert.error_matches(function ()
248 st.stanza("valid"):text(value);
249 end, value_type);
250 end);
253 end);
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")));
259 end);
260 it("identifies strings as not stanzas", function ()
261 assert.falsy(st.is_stanza(""));
262 end);
263 it("identifies numbers as not stanzas", function ()
264 assert.falsy(st.is_stanza(1));
265 end);
266 it("identifies tables as not stanzas", function ()
267 assert.falsy(st.is_stanza({}));
268 end);
269 end);
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"));
287 s:remove_children();
288 assert.falsy(s.tags[1]);
289 end);
290 end);
292 describe("#maptags", function ()
293 it("should work", function ()
294 local s = st.stanza("test")
295 :tag("one"):up()
296 :tag("two"):up()
297 :tag("one"):up()
298 :tag("three"):up();
300 local function one_filter(tag)
301 if tag.name == "one" then
302 return nil;
304 return tag;
306 assert.equal(4, #s.tags);
307 s:maptags(one_filter);
308 assert.equal(2, #s.tags);
309 end);
311 it("should work with multiple consecutive text nodes", function ()
312 local s = st.deserialize({
313 "\n";
315 "away";
316 name = "show";
317 attr = {};
319 "\n";
321 "I am away";
322 name = "status";
323 attr = {};
325 "\n";
327 "0";
328 name = "priority";
329 attr = {};
331 "\n";
333 name = "c";
334 attr = {
335 xmlns = "http://jabber.org/protocol/caps";
336 node = "http://psi-im.org";
337 hash = "sha-1";
340 "\n";
341 "\n";
342 name = "presence";
343 attr = {
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
356 return nil;
358 return tag;
359 end);
360 assert.equal(3, #s.tags);
361 end);
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");
368 end);
369 end);
371 describe("#clone", function ()
372 it("works", function ()
373 local s = st.message({type="chat"}, "Hello"):reset();
374 local c = st.clone(s);
375 assert.same(s, c);
376 end);
378 it("works", function ()
379 assert.has_error(function ()
380 st.clone("this is not a stanza");
381 end);
382 end);
383 end);
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);
399 end);
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"]);
413 end);
414 end);
415 end);