Merge 0.10->0.11
[prosody.git] / spec / util_stanza_spec.lua
blob6fbae41a73883182b7599e57deb9fe405ae3d1ef
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({ id = "foo" });
99 assert.are.equal("iq", i.name);
100 assert.are.equal("foo", i.attr.id);
101 end);
103 it("should reject stanzas with no id", function ()
104 assert.has.error_match(function ()
105 st.iq();
106 end, "id attribute");
108 assert.has.error_match(function ()
109 st.iq({ foo = "bar" });
110 end, "id attribute");
111 end);
112 end);
114 describe("#presence()", function ()
115 it("should work", function()
116 local p = st.presence();
117 assert.are.equal(p.name, "presence");
118 end);
119 end);
121 describe("#reply()", function()
122 it("should work for <s>", function()
123 -- Test stanza
124 local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" })
125 :tag("child1");
126 -- Make reply stanza
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");
133 end);
135 it("should work for <iq get>", function()
136 -- Test stanza
137 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
138 :tag("child1");
139 -- Make reply stanza
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");
147 end);
149 it("should work for <iq set>", function()
150 -- Test stanza
151 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "set" })
152 :tag("child1");
153 -- Make reply stanza
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");
161 end);
162 end);
164 describe("#error_reply()", function()
165 it("should work for <s>", function()
166 -- Test stanza
167 local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" })
168 :tag("child1");
169 -- Make reply stanza
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");
177 end);
179 it("should work for <iq get>", function()
180 -- Test stanza
181 local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
182 :tag("child1");
183 -- Make reply stanza
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");
192 end);
193 end);
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 ()
208 st.stanza(value);
209 end, value_type);
210 end);
211 it(value_type.." in attribute names", function ()
212 assert.error_matches(function ()
213 st.stanza("valid", { [value] = "valid" });
214 end, value_type);
215 end);
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 ()
221 st.stanza(value);
222 end, value_type);
223 end);
224 it(value_type.." in attribute names", function ()
225 assert.error_matches(function ()
226 st.stanza("valid", { [value] = "valid" });
227 end, value_type);
228 end);
229 if value ~= nil then
230 it(value_type.." in attribute values", function ()
231 assert.error_matches(function ()
232 st.stanza("valid", { valid = value });
233 end, value_type);
234 end);
235 it(value_type.." in text node", function ()
236 assert.error_matches(function ()
237 st.stanza("valid"):text(value);
238 end, value_type);
239 end);
242 end);
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")));
248 end);
249 it("identifies strings as not stanzas", function ()
250 assert.falsy(st.is_stanza(""));
251 end);
252 it("identifies numbers as not stanzas", function ()
253 assert.falsy(st.is_stanza(1));
254 end);
255 it("identifies tables as not stanzas", function ()
256 assert.falsy(st.is_stanza({}));
257 end);
258 end);
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"));
276 s:remove_children();
277 assert.falsy(s.tags[1]);
278 end);
279 end);
281 describe("#maptags", function ()
282 it("should work", function ()
283 local s = st.stanza("test")
284 :tag("one"):up()
285 :tag("two"):up()
286 :tag("one"):up()
287 :tag("three"):up();
289 local function one_filter(tag)
290 if tag.name == "one" then
291 return nil;
293 return tag;
295 assert.equal(4, #s.tags);
296 s:maptags(one_filter);
297 assert.equal(2, #s.tags);
298 end);
300 it("should work with multiple consecutive text nodes", function ()
301 local s = st.deserialize({
302 "\n";
304 "away";
305 name = "show";
306 attr = {};
308 "\n";
310 "I am away";
311 name = "status";
312 attr = {};
314 "\n";
316 "0";
317 name = "priority";
318 attr = {};
320 "\n";
322 name = "c";
323 attr = {
324 xmlns = "http://jabber.org/protocol/caps";
325 node = "http://psi-im.org";
326 hash = "sha-1";
329 "\n";
330 "\n";
331 name = "presence";
332 attr = {
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
345 return nil;
347 return tag;
348 end);
349 assert.equal(3, #s.tags);
350 end);
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");
357 end);
358 end);
360 describe("#clone", function ()
361 it("works", function ()
362 local s = st.message({type="chat"}, "Hello"):reset();
363 local c = st.clone(s);
364 assert.same(s, c);
365 end);
367 it("works", function ()
368 assert.has_error(function ()
369 st.clone("this is not a stanza");
370 end);
371 end);
372 end);
373 end);