blob: 11b524efda4dce7de18ea27407b0be77e5302594 [file]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/analytics/cpp/google_analytics_4/client.h"
#include <cstddef>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/analytics/cpp/google_analytics_4/event.h"
#include "src/lib/fxl/strings/substitute.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
#include "third_party/rapidjson/include/rapidjson/prettywriter.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
namespace rapidjson {
std::ostream& operator<<(std::ostream& os, const Document& doc) {
StringBuffer s;
PrettyWriter<StringBuffer> writer(s);
writer.SetIndent(' ', 2);
doc.Accept(writer);
return os << s.GetString();
}
} // namespace rapidjson
namespace analytics::google_analytics_4 {
class MockClient : public Client {
public:
MockClient() : Client(2) {}
void expectEqData(const std::vector<std::string>& data) {
ASSERT_EQ(data.size(), data_.size());
for (size_t i = 0; i < data.size(); i++) {
rapidjson::Document other, self;
other.Parse(data[i]);
self.Parse(data_[i]);
EXPECT_EQ(self, other);
}
}
private:
void SendData(std::string body) override { data_.push_back(std::move(body)); }
std::vector<std::string> data_;
};
class MockEvent : public Event {
public:
using Event::SetParameter;
explicit MockEvent(std::string name) : Event(std::move(name)) {}
};
class ClientTest : public ::testing::Test {
protected:
void SetUp() override {
client_.SetQueryParameters("IGNORED_IN_TEST_BUT_NEEDED", "IGNORED_IN_TEST_BUT_NEEDED");
client_.SetClientId("TEST-CLIENT");
}
MockClient client_;
};
// When a null event is added, the library should not crash.
// Adding a null event is something we don't expect to happen, so we only
// require that the library does not crash. Particularly, the generated json is
// probably an invalid GA4 payload, which will be just a no-op to the GA4
// server.
TEST_F(ClientTest, NullEvent) {
std::unique_ptr<Event> event(nullptr);
std::string body =
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": []
}
)JSON";
client_.AddEvent(std::move(event));
std::vector<std::string> data{body};
client_.expectEqData(data);
}
TEST_F(ClientTest, SimpleEvent) {
auto event = std::make_unique<MockEvent>("test-event");
auto body = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"timestamp_micros": $0
}
]
}
)JSON",
std::to_string(event->timestamp_micros().count()));
client_.AddEvent(std::move(event));
std::vector<std::string> data{body};
client_.expectEqData(data);
}
TEST_F(ClientTest, SimpleEventWithUserProperty) {
client_.SetUserProperty("test-key", "test-val");
auto event = std::make_unique<MockEvent>("test-event");
auto body = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"timestamp_micros": $0
}
],
"user_properties": {"test-key": {"value": "test-val"}}
}
)JSON",
std::to_string(event->timestamp_micros().count()));
client_.AddEvent(std::move(event));
std::vector<std::string> data{body};
client_.expectEqData(data);
}
TEST_F(ClientTest, SimpleEventWithUserProperties) {
client_.SetUserProperty("test-key-1", "test-val-1");
client_.SetUserProperty("test-key-2", 2);
client_.SetUserProperty("test-key-3", false);
client_.SetUserProperty("test-key-4", 2.5);
auto event = std::make_unique<MockEvent>("test-event");
auto body = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"timestamp_micros": $0
}
],
"user_properties": {
"test-key-1": {"value": "test-val-1"},
"test-key-2": {"value": 2},
"test-key-3": {"value": false},
"test-key-4": {"value": 2.5}
}
}
)JSON",
std::to_string(event->timestamp_micros().count()));
client_.AddEvent(std::move(event));
std::vector<std::string> data{body};
client_.expectEqData(data);
}
TEST_F(ClientTest, ConsecutiveEventsWithUserProperties) {
client_.SetUserProperty("test-key-1", "test-val-1");
client_.SetUserProperty("test-key-2", "test-val-2");
auto event1 = std::make_unique<MockEvent>("test-event-1");
auto body1 = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event-1",
"timestamp_micros": $0
}
],
"user_properties": {
"test-key-1": {"value": "test-val-1"},
"test-key-2": {"value": "test-val-2"}
}
}
)JSON",
std::to_string(event1->timestamp_micros().count()));
client_.AddEvent(std::move(event1));
auto event2 = std::make_unique<MockEvent>("test-event-2");
auto body2 = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event-2",
"timestamp_micros": $0
}
],
"user_properties": {
"test-key-1": {"value": "test-val-1"},
"test-key-2": {"value": "test-val-2"}
}
}
)JSON",
std::to_string(event2->timestamp_micros().count()));
client_.AddEvent(std::move(event2));
std::vector<std::string> data{body1, body2};
client_.expectEqData(data);
}
TEST_F(ClientTest, EventWithParameters) {
auto event = std::make_unique<MockEvent>("test-event");
event->SetParameter("test-key-1", "test-val-1");
event->SetParameter("test-key-2", 2);
event->SetParameter("test-key-3", false);
event->SetParameter("test-key-4", 2.5);
auto body = fxl::Substitute(
R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"test-key-1": "test-val-1",
"test-key-2": 2,
"test-key-3": false,
"test-key-4": 2.5
},
"timestamp_micros": $0
}
]
}
)JSON",
std::to_string(event->timestamp_micros().count()));
client_.AddEvent(std::move(event));
std::vector<std::string> data{body};
client_.expectEqData(data);
}
TEST_F(ClientTest, AddEventsLessThanBatchSize) {
size_t batch_size = 3;
client_.SetUserProperty("user", "property");
auto event = std::make_unique<MockEvent>("test-event");
event->SetParameter("order", 0);
std::string body = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event->timestamp_micros().count()));
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::move(event));
std::vector<std::string> data{body};
client_.AddEvents(std::move(events), batch_size);
client_.expectEqData(data);
}
TEST_F(ClientTest, AddEventsEqualBatchSize) {
size_t batch_size = 3;
client_.SetUserProperty("user", "property");
auto event0 = std::make_unique<MockEvent>("test-event");
event0->SetParameter("order", 0);
auto event1 = std::make_unique<MockEvent>("other-event");
event1->SetParameter("order", 1);
auto event2 = std::make_unique<MockEvent>("test-event");
event2->SetParameter("order", 2);
std::string body = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
},
{
"name": "other-event",
"params": {
"order": 1
},
"timestamp_micros": $1
},
{
"name": "test-event",
"params": {
"order": 2
},
"timestamp_micros": $2
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event0->timestamp_micros().count()),
std::to_string(event1->timestamp_micros().count()),
std::to_string(event2->timestamp_micros().count()));
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::move(event0));
events.push_back(std::move(event1));
events.push_back(std::move(event2));
std::vector<std::string> data{body};
client_.AddEvents(std::move(events), batch_size);
client_.expectEqData(data);
}
TEST_F(ClientTest, AddEventsLargerThanBatchSize) {
size_t batch_size = 2;
client_.SetUserProperty("user", "property");
auto event0 = std::make_unique<MockEvent>("test-event");
event0->SetParameter("order", 0);
auto event1 = std::make_unique<MockEvent>("other-event");
event1->SetParameter("order", 1);
auto event2 = std::make_unique<MockEvent>("test-event");
event2->SetParameter("order", 2);
std::string body1 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
},
{
"name": "other-event",
"params": {
"order": 1
},
"timestamp_micros": $1
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event0->timestamp_micros().count()),
std::to_string(event1->timestamp_micros().count()));
std::string body2 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 2
},
"timestamp_micros": $0
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event2->timestamp_micros().count()));
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::move(event0));
events.push_back(std::move(event1));
events.push_back(std::move(event2));
std::vector<std::string> data{body1, body2};
client_.AddEvents(std::move(events), batch_size);
client_.expectEqData(data);
}
TEST_F(ClientTest, AddEventsDoublesBatchSize) {
size_t batch_size = 2;
client_.SetUserProperty("user", "property");
auto event0 = std::make_unique<MockEvent>("test-event");
event0->SetParameter("order", 0);
auto event1 = std::make_unique<MockEvent>("other-event");
event1->SetParameter("order", 1);
auto event2 = std::make_unique<MockEvent>("test-event");
event2->SetParameter("order", 2);
auto event3 = std::make_unique<MockEvent>("test-event");
event3->SetParameter("order", 3);
std::string body1 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
},
{
"name": "other-event",
"params": {
"order": 1
},
"timestamp_micros": $1
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event0->timestamp_micros().count()),
std::to_string(event1->timestamp_micros().count()));
std::string body2 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 2
},
"timestamp_micros": $0
},
{
"name": "test-event",
"params": {
"order": 3
},
"timestamp_micros": $1
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event2->timestamp_micros().count()),
std::to_string(event3->timestamp_micros().count()));
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::move(event0));
events.push_back(std::move(event1));
events.push_back(std::move(event2));
events.push_back(std::move(event3));
std::vector<std::string> data{body1, body2};
client_.AddEvents(std::move(events), batch_size);
client_.expectEqData(data);
}
// When there is a null event mixed in a vector of events, the library should
// not crash. Note that the number of events in each batch might be affected.
// We allow this behavior since (1) we do not expect having null events
// (2) the difference made on the batch will eventually lead to the same
// result.
TEST_F(ClientTest, NullInEvents) {
size_t batch_size = 2;
client_.SetUserProperty("user", "property");
auto event0 = std::make_unique<MockEvent>("test-event");
event0->SetParameter("order", 0);
auto event1 = std::make_unique<MockEvent>("other-event");
event1->SetParameter("order", 1);
auto event2 = std::make_unique<MockEvent>("test-event");
event2->SetParameter("order", 2);
std::unique_ptr<Event> event_null(nullptr);
std::string body1 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
},
{
"name": "other-event",
"params": {
"order": 1
},
"timestamp_micros": $1
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event0->timestamp_micros().count()),
std::to_string(event1->timestamp_micros().count()));
std::string body2 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 2
},
"timestamp_micros": $0
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event2->timestamp_micros().count()));
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::move(event0));
events.push_back(std::move(event1));
events.push_back(std::move(event_null));
events.push_back(std::move(event2));
std::vector<std::string> data{body1, body2};
client_.AddEvents(std::move(events), batch_size);
client_.expectEqData(data);
}
TEST_F(ClientTest, AddEventsToBatch) {
client_.SetUserProperty("user", "property");
auto event0 = std::make_unique<MockEvent>("test-event");
event0->SetParameter("order", 0);
auto event1 = std::make_unique<MockEvent>("other-event");
event1->SetParameter("order", 1);
auto event2 = std::make_unique<MockEvent>("test-event");
event2->SetParameter("order", 2);
std::string body1 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 0
},
"timestamp_micros": $0
},
{
"name": "other-event",
"params": {
"order": 1
},
"timestamp_micros": $1
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event0->timestamp_micros().count()),
std::to_string(event1->timestamp_micros().count()));
std::string body2 = fxl::Substitute(R"JSON(
{
"client_id": "TEST-CLIENT",
"events": [
{
"name": "test-event",
"params": {
"order": 2
},
"timestamp_micros": $0
}
],
"user_properties": {
"user": {"value": "property"}
}
}
)JSON",
std::to_string(event2->timestamp_micros().count()));
std::vector<std::string> data{body1, body2};
client_.AddEventToDefaultBatch(std::move(event0));
client_.AddEventToDefaultBatch(std::move(event1));
client_.AddEventToDefaultBatch(std::move(event2));
client_.SendDefaultBatch();
client_.expectEqData(data);
}
} // namespace analytics::google_analytics_4