blob: 578e6adea817198110873e1b1f18f1f3ac50b27e [file] [log] [blame]
Alex Vakulenkobf71f702015-05-18 14:30:56 -07001// Copyright 2015 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "buffet/notification/xmpp_stream_parser.h"
6
7#include <memory>
8#include <vector>
9#include <gtest/gtest.h>
10
11#include "buffet/notification/xml_node.h"
12
13namespace buffet {
14namespace {
15// Use some real-world XMPP stream snippet to make sure all the expected
16// elements are parsed properly.
17const char kXmppStreamData[] =
18 "<stream:stream from=\"clouddevices.gserviceaccount.com\" id=\"76EEB8FDB449"
19 "5558\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" x"
20 "mlns=\"jabber:client\">"
21 "<stream:features><starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"><requ"
22 "ired/></starttls><mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><m"
23 "echanism>X-OAUTH2</mechanism><mechanism>X-GOOGLE-TOKEN</mechanism></mechan"
24 "isms></stream:features>"
25 "<message from=\"cloud-devices@clouddevices.google.com/srvenc-xgbCfg9hX6tCp"
26 "xoMYsExqg==\" to=\"4783f652b387449fc52a76f9a16e616f@clouddevices.gservicea"
27 "ccount.com/5A85ED9C\"><push:push channel=\"cloud_devices\" xmlns:push=\"go"
28 "ogle:push\"><push:recipient to=\"4783f652b387449fc52a76f9a16e616f@clouddev"
29 "ices.gserviceaccount.com\"></push:recipient><push:data>eyJraW5kIjoiY2xvdWR"
30 "kZXZpY2VzI25vdGlmaWNhdGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kS"
31 "WQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI"
32 "5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05N"
33 "jA1LTFlNGFjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5"
34 "kIiwiaWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01N"
35 "jExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM"
36 "3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOiJxdWV1Z"
37 "WQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIxNDMxNTY0NDY"
38 "4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleHBpcmF0aW9uVGltZ"
39 "W91dE1zIjoiMzYwMDAwMCJ9fQ==</push:data></push:push></message>";
40
41} // anonymous namespace
42
43class XmppStreamParserTest : public testing::Test,
44 public XmppStreamParser::Delegate {
45 public:
46 void SetUp() override {
47 parser_.reset(new XmppStreamParser{this});
48 }
49
50 void OnStreamStart(const std::string& node_name,
51 std::map<std::string, std::string> attributes) override {
52 EXPECT_FALSE(stream_started_);
53 stream_started_ = true;
54 stream_start_node_name_ = node_name;
55 stream_start_node_attributes_ = std::move(attributes);
56 }
57
58 void OnStreamEnd(const std::string& node_name) override {
59 EXPECT_TRUE(stream_started_);
60 EXPECT_EQ(stream_start_node_name_, node_name);
61 stream_started_ = false;
62 }
63
64 void OnStanza(std::unique_ptr<XmlNode> stanza) override {
65 stanzas_.push_back(std::move(stanza));
66 }
67
68 void Reset() {
69 parser_.reset(new XmppStreamParser{this});
70 stream_started_ = false;
71 stream_start_node_name_.clear();
72 stream_start_node_attributes_.clear();
73 stanzas_.clear();
74 }
75
76 std::unique_ptr<XmppStreamParser> parser_;
77 bool stream_started_{false};
78 std::string stream_start_node_name_;
79 std::map<std::string, std::string> stream_start_node_attributes_;
80 std::vector<std::unique_ptr<XmlNode>> stanzas_;
81};
82
83TEST_F(XmppStreamParserTest, InitialState) {
84 EXPECT_FALSE(stream_started_);
85 EXPECT_TRUE(stream_start_node_name_.empty());
86 EXPECT_TRUE(stream_start_node_attributes_.empty());
87 EXPECT_TRUE(stanzas_.empty());
88}
89
90TEST_F(XmppStreamParserTest, FullStartElement) {
91 parser_->ParseData("<foo bar=\"baz\" quux=\"1\">");
92 EXPECT_TRUE(stream_started_);
93 EXPECT_EQ("foo", stream_start_node_name_);
94 const std::map<std::string, std::string> expected_attrs{
95 {"bar", "baz"},
96 {"quux", "1"}
97 };
98 EXPECT_EQ(expected_attrs, stream_start_node_attributes_);
99}
100
101TEST_F(XmppStreamParserTest, PartialStartElement) {
102 parser_->ParseData("<foo bar=\"baz");
103 EXPECT_FALSE(stream_started_);
104 EXPECT_TRUE(stream_start_node_name_.empty());
105 EXPECT_TRUE(stream_start_node_attributes_.empty());
106 EXPECT_TRUE(stanzas_.empty());
107 parser_->ParseData("\" quux");
108 EXPECT_FALSE(stream_started_);
109 parser_->ParseData("=\"1\">");
110 EXPECT_TRUE(stream_started_);
111 EXPECT_EQ("foo", stream_start_node_name_);
112 const std::map<std::string, std::string> expected_attrs{
113 {"bar", "baz"},
114 {"quux", "1"}
115 };
116 EXPECT_EQ(expected_attrs, stream_start_node_attributes_);
117}
118
119TEST_F(XmppStreamParserTest, VariableLengthPackets) {
120 std::string value;
121 const std::string xml_data = kXmppStreamData;
122 const std::map<std::string, std::string> expected_stream_attrs{
123 {"from", "clouddevices.gserviceaccount.com"},
124 {"id", "76EEB8FDB4495558"},
125 {"version", "1.0"},
126 {"xmlns:stream", "http://etherx.jabber.org/streams"},
127 {"xmlns", "jabber:client"}
128 };
129 // Try splitting the data into pieces from 1 character in size to the whole
130 // data block and verify that we still can parse the whole message correctly.
131 // Here |step| is the size of each individual data chunk.
132 for (size_t step = 1; step <= xml_data.size(); step++) {
133 // Feed each individual chunk to the parser and hope it can piece everything
134 // together correctly.
135 for (size_t pos = 0; pos < xml_data.size(); pos += step) {
136 parser_->ParseData(xml_data.substr(pos, step));
137 }
138 EXPECT_TRUE(stream_started_);
139 EXPECT_EQ("stream:stream", stream_start_node_name_);
140 EXPECT_EQ(expected_stream_attrs, stream_start_node_attributes_);
141 EXPECT_EQ(2u, stanzas_.size());
142
143 const XmlNode* stanza1 = stanzas_[0].get();
144 EXPECT_EQ("stream:features", stanza1->name());
145 ASSERT_EQ(2u, stanza1->children().size());
146 const XmlNode* child1 = stanza1->children()[0].get();
147 EXPECT_EQ("starttls", child1->name());
148 ASSERT_EQ(1u, child1->children().size());
149 EXPECT_EQ("required", child1->children()[0]->name());
150 const XmlNode* child2 = stanza1->children()[1].get();
151 EXPECT_EQ("mechanisms", child2->name());
152 ASSERT_EQ(2u, child2->children().size());
153 EXPECT_EQ("mechanism", child2->children()[0]->name());
154 EXPECT_EQ("X-OAUTH2", child2->children()[0]->text());
155 EXPECT_EQ("mechanism", child2->children()[1]->name());
156 EXPECT_EQ("X-GOOGLE-TOKEN", child2->children()[1]->text());
157
158 const XmlNode* stanza2 = stanzas_[1].get();
159 EXPECT_EQ("message", stanza2->name());
160 ASSERT_EQ(2u, stanza2->attributes().size());
161 EXPECT_TRUE(stanza2->GetAttribute("from", &value));
162 EXPECT_EQ("cloud-devices@clouddevices.google.com/"
163 "srvenc-xgbCfg9hX6tCpxoMYsExqg==", value);
164 EXPECT_TRUE(stanza2->GetAttribute("to", &value));
165 EXPECT_EQ("4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount."
166 "com/5A85ED9C", value);
167 ASSERT_EQ(1u, stanza2->children().size());
168
169 const XmlNode* child = stanza2->children().back().get();
170 EXPECT_EQ("push:push", child->name());
171 ASSERT_EQ(2u, child->attributes().size());
172 EXPECT_TRUE(child->GetAttribute("channel", &value));
173 EXPECT_EQ("cloud_devices", value);
174 EXPECT_TRUE(child->GetAttribute("xmlns:push", &value));
175 EXPECT_EQ("google:push", value);
176 ASSERT_EQ(2u, child->children().size());
177
178 child1 = child->children()[0].get();
179 EXPECT_EQ("push:recipient", child1->name());
180 ASSERT_EQ(1u, child1->attributes().size());
181 EXPECT_TRUE(child1->GetAttribute("to", &value));
182 EXPECT_EQ("4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount."
183 "com", value);
184 EXPECT_TRUE(child1->children().empty());
185
186 child2 = child->children()[1].get();
187 EXPECT_EQ("push:data", child2->name());
188 EXPECT_TRUE(child2->attributes().empty());
189 EXPECT_TRUE(child2->children().empty());
190 const std::string expected_data = "eyJraW5kIjoiY2xvdWRkZXZpY2VzI25vdGlmaWNh"
191 "dGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kSWQiOiIwNWE3MTA5MC"
192 "1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI5ODAtOTkyMy0y"
193 "Njc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05NjA1LTFlNG"
194 "FjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5kIiwi"
195 "aWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01Nj"
196 "ExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgt"
197 "YzM3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOi"
198 "JxdWV1ZWQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIx"
199 "NDMxNTY0NDY4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleH"
200 "BpcmF0aW9uVGltZW91dE1zIjoiMzYwMDAwMCJ9fQ==";
201 EXPECT_EQ(expected_data, child2->text());
202 }
203}
204
205} // namespace buffet