Skip to content

Commit b549f4c

Browse files
authored
Merge pull request #1 from thoughtworks-hpc/add-unit-test
Add unit test
2 parents 6a51fcc + 3578434 commit b549f4c

13 files changed

+373
-12
lines changed

rest/configuration.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include "generator.h"
88

9+
#include <unordered_map>
10+
911
constexpr auto DR_EVILS_DARK_ENERGY_ENERGY_SUPPLIER = "Dr Evil's Dark Energy";
1012
constexpr auto THE_GREEN_ECO_ENERGY_SUPPLIER = "The Green Eco";
1113
constexpr auto POWER_FOR_EVERYONE_ENERGY_SUPPLIER = "Power for Everyone";

rest/controller/MeterReadingController.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class MeterReadingController {
6464
auto body = nlohmann::json::parse(req.body());
6565
auto smartMeterId = body["smartMeterId"];
6666
std::vector<ElectricityReading> electricityReadings;
67+
if (!IsMeterReadingsValid(smartMeterId, body["electricityReadings"])) {
68+
return {http::status::internal_server_error, 11};
69+
}
6770
for (auto &electricityReading : body["electricityReadings"]) {
6871
electricityReadings.emplace_back(detail::fromRfc3339(electricityReading["time"]), electricityReading["reading"]);
6972
}
@@ -74,6 +77,13 @@ class MeterReadingController {
7477
private:
7578
ElectricityReadingService &electricityReadingService;
7679
MeterReadingService &meterReadingService;
80+
bool IsMeterReadingsValid(const nlohmann::basic_json<> &smartMeterId,
81+
const nlohmann::basic_json<> &electricityReadings) {
82+
if (smartMeterId.type() == nlohmann::json::value_t::null || electricityReadings.empty()) {
83+
return false;
84+
}
85+
return true;
86+
}
7787
};
7888

7989
#endif // DEVELOPER_JOYOFENERGY_CPP_BEAST_METERREADINGCONTROLLER_H

rest/controller/PricePlanComparatorController.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ class PricePlanComparatorController {
3131
res.set(http::field::content_type, "application/json");
3232
res.keep_alive(req.keep_alive());
3333
nlohmann::json j;
34-
j["pricePlanComparisons"] = {{"price-plan-0", double(costs.value()["price-plan-0"]) / 10000},
35-
{"price-plan-1", double(costs.value()["price-plan-1"]) / 10000},
36-
{"price-plan-2", double(costs.value()["price-plan-2"]) / 10000}};
34+
for (auto &ele : costs.value()) {
35+
ele.second /= 10000;
36+
}
37+
j["pricePlanComparisons"] = costs.value();
3738
j["pricePlanId"] = current_price_plans[meterId];
3839
res.body() = j.dump();
3940
res.prepare_payload();
@@ -43,17 +44,22 @@ class PricePlanComparatorController {
4344
http::response<http::string_body> Recommend(const http::request<http::string_body> &req,
4445
const std::vector<std::string> &queries) {
4546
const auto &meterId = queries[0];
46-
int limit = std::stoi(queries[2]);
47+
std::optional<int> maybeLimit;
48+
if (queries.size() > 2){
49+
maybeLimit = std::stoi(queries[2]);
50+
}
4751
auto costs = pricePlanService.getConsumptionCostOfElectricityReadingsForEachPricePlan(meterId);
4852

4953
if (!costs) {
5054
return {http::status::not_found, req.version()};
5155
}
5256

53-
std::vector<std::pair<std::string, float>> ordered_costs{costs->begin(), costs->end()};
57+
std::vector<std::pair<std::string, double>> ordered_costs{costs->begin(), costs->end()};
5458
std::sort(ordered_costs.begin(), ordered_costs.end(),
5559
[](auto &cost_a, auto &cost_b) { return cost_a.second < cost_b.second; });
56-
ordered_costs.resize(std::min(limit, int(ordered_costs.size())));
60+
if (maybeLimit.has_value()) {
61+
ordered_costs.resize(std::min(maybeLimit.value(), int(ordered_costs.size())));
62+
}
5763

5864
http::response<http::string_body> res{http::status::ok, req.version()};
5965
res.set(http::field::content_type, "application/json");

rest/domain/ElectricityReading.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class ElectricityReading {
1414

1515
size_t getReading() const { return reading; }
1616

17+
bool operator==(const ElectricityReading& rhs) const { return time == rhs.time && reading == rhs.reading; }
18+
bool operator!=(const ElectricityReading& rhs) const { return !(rhs == *this); }
19+
1720
private:
1821
time_point_type time;
1922
size_t reading;

rest/domain/PricePlan.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,52 @@
22
#define DEVELOPER_JOYOFENERGY_CPP_BEAST_PRICEPLAN_H
33

44
#include <string>
5+
#include <utility>
56
#include <vector>
67

78
class PricePlan {
89
// todo:
910
public:
11+
using time_point_type = std::chrono::time_point<std::chrono::system_clock>;
1012
class PeakTimeMultiplier {
13+
public:
1114
enum DayOfWeek {
15+
SUNDAY,
1216
MONDAY,
1317
TUESDAY,
18+
WEDNESDAY,
19+
THURSDAY,
20+
FRIDAY,
21+
SATURDAY
1422
};
1523

24+
PeakTimeMultiplier(DayOfWeek dayOfWeek, int multiplier) : dayOfWeek(dayOfWeek), multiplier(multiplier) {}
25+
1626
DayOfWeek dayOfWeek;
1727
int multiplier;
18-
19-
PeakTimeMultiplier(DayOfWeek dayOfWeek, int multiplier) : dayOfWeek(dayOfWeek), multiplier(multiplier) {}
2028
};
2129

2230
PricePlan(std::string planName, std::string energySupplier, int unitRate, std::vector<PeakTimeMultiplier> peakTimeMultipliers)
23-
: planName(planName), energySupplier(energySupplier), unitRate(unitRate), peakTimeMultipliers(peakTimeMultipliers) {}
31+
: planName(std::move(planName)), energySupplier(std::move(energySupplier)), unitRate(unitRate), peakTimeMultipliers(std::move(peakTimeMultipliers)) {}
2432

2533
std::string getEnergySupplier() const { return energySupplier; }
2634

2735
std::string getPlanName() const { return planName; }
2836

2937
int getUnitRate() const { return unitRate; }
3038

39+
int getPrice(time_point_type dateTime) const {
40+
auto time_t_dateTime = std::chrono::system_clock::to_time_t(dateTime);
41+
auto t = std::localtime(&time_t_dateTime);
42+
auto it = std::find_if(peakTimeMultipliers.begin(), peakTimeMultipliers.end(), [=](auto &p) {
43+
return p.dayOfWeek == t->tm_wday;
44+
});
45+
if (it == peakTimeMultipliers.end()) {
46+
return unitRate;
47+
}
48+
return unitRate * it->multiplier;
49+
}
50+
3151
private:
3252
const std::string energySupplier;
3353
const std::string planName;

rest/service/MeterReadingService.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <iostream>
1111
#include <map>
12+
#include <unordered_map>
1213
#include <optional>
1314
#include <string>
1415
#include <vector>

rest/service/PricePlanService.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#define DEVELOPER_JOYOFENERGY_CPP_BEAST_PRICEPLANSERVICE_H
33

44
#include <configuration.h>
5-
#include <controller/MeterReadingController.h>
65
#include <domain/ElectricityReading.h>
76
#include <domain/PricePlan.h>
87
#include <service/MeterReadingService.h>

test/CMakeLists.txt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,19 @@ find_package(GTest REQUIRED)
44
find_package(nlohmann_json REQUIRED)
55
target_link_libraries(endpoint_test PRIVATE GTest::gmock_main nlohmann_json::nlohmann_json rest)
66

7-
add_test(endpoint_test endpoint_test)
7+
add_executable(controller_test)
8+
target_sources(controller_test PRIVATE controller/MeterReadingControllerTest.cpp controller/PricePlanComparatorControllerTest.cpp)
9+
target_link_libraries(controller_test PRIVATE GTest::gmock_main nlohmann_json::nlohmann_json rest)
10+
11+
add_executable(domain_test)
12+
target_sources(domain_test PRIVATE domain/PricePlanTest.cpp)
13+
target_link_libraries(domain_test PRIVATE GTest::gmock_main rest)
14+
15+
add_executable(service_test)
16+
target_sources(service_test PRIVATE service/MeterReadingServiceTest.cpp)
17+
target_link_libraries(service_test PRIVATE GTest::gmock_main rest)
18+
19+
add_test(endpoint_test endpoint_test)
20+
add_test(controller_test controller_test)
21+
add_test(domain_test domain_test)
22+
add_test(service_test service_test)

test/ReadingTest.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include <nlohmann/json.hpp>
22

3-
#include "domain/ElectricityReading.h"
43
#include "generator.h"
54
#include "test/EndpointTest.h"
65

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#include <gmock/gmock.h>
2+
#include <rest/controller/MeterReadingController.h>
3+
4+
#include <boost/beast/http.hpp>
5+
#include <nlohmann/json.hpp>
6+
7+
using nlohmann::json;
8+
using ::testing::Eq;
9+
10+
namespace http = boost::beast::http;
11+
12+
class MeterReadingControllerTest : public ::testing::Test {
13+
protected:
14+
std::unordered_map<std::string, std::vector<ElectricityReading>> meterAssociatedReadings;
15+
ElectricityReadingService electricityReadingService{meterAssociatedReadings};
16+
MeterReadingService meterReadingService{meterAssociatedReadings};
17+
MeterReadingController controller{electricityReadingService, meterReadingService};
18+
19+
http::request<http::string_body> BuildRequest(http::verb verb, boost::string_view target, const json &request_body) {
20+
http::request<http::string_body> req{verb, target, 11};
21+
req.set(http::field::content_type, "application/json");
22+
req.body() = request_body.dump();
23+
req.prepare_payload();
24+
return req;
25+
}
26+
};
27+
28+
TEST_F(MeterReadingControllerTest, StoreShouldResponseWithErrorGivenNoMeterIdIsSupplied) {
29+
auto req = BuildRequest(http::verb::post, "/readings/store", R"({})"_json);
30+
std::vector<std::string> queries;
31+
32+
auto response = controller.Store(req, queries);
33+
34+
EXPECT_THAT(response.result(), Eq(http::status::internal_server_error));
35+
}
36+
37+
TEST_F(MeterReadingControllerTest, StoreShouldResponseWithErrorGivenEmptyMeterReading) {
38+
json body = R"({
39+
"smartMeterId": "smart-meter-0",
40+
"electricityReadings": []
41+
})"_json;
42+
auto req = BuildRequest(http::verb::post, "/readings/store", body);
43+
std::vector<std::string> queries;
44+
45+
auto response = controller.Store(req, queries);
46+
47+
EXPECT_THAT(response.result(), Eq(http::status::internal_server_error));
48+
}
49+
50+
TEST_F(MeterReadingControllerTest, StoreShouldResponseWithErrorGivenNoMeterReadingIsSupplied) {
51+
json body = R"({
52+
"smartMeterId": "smart-meter-0"
53+
})"_json;
54+
auto req = BuildRequest(http::verb::post, "/readings/store", body);
55+
std::vector<std::string> queries;
56+
57+
auto response = controller.Store(req, queries);
58+
59+
EXPECT_THAT(response.result(), Eq(http::status::internal_server_error));
60+
}
61+
62+
TEST_F(MeterReadingControllerTest, StoreShouldStoreGivenMultipleBatchesOfMeterReadings) {
63+
json body1 = R"({
64+
"smartMeterId": "smart-meter-0",
65+
"electricityReadings": [
66+
{
67+
"time": "2021-08-18T06:42:15.725202Z",
68+
"reading": 1
69+
}
70+
]
71+
})"_json;
72+
json body2 = R"({
73+
"smartMeterId": "smart-meter-0",
74+
"electricityReadings": [
75+
{
76+
"time": "2021-08-18T06:44:15.725202Z",
77+
"reading": 2
78+
}
79+
]
80+
})"_json;
81+
auto req1 = BuildRequest(http::verb::post, "/readings/store", body1);
82+
auto req2 = BuildRequest(http::verb::post, "/readings/store", body2);
83+
std::vector<std::string> queries;
84+
85+
controller.Store(req1, queries);
86+
controller.Store(req2, queries);
87+
88+
std::vector<ElectricityReading> expectedElectricityReadings = {
89+
{detail::fromRfc3339("2021-08-18T06:42:15.725202Z"), 1},
90+
{detail::fromRfc3339("2021-08-18T06:44:15.725202Z"), 2}
91+
};
92+
93+
EXPECT_THAT(meterReadingService.getReadings("smart-meter-0"), Eq(expectedElectricityReadings));
94+
}
95+
96+
TEST_F(MeterReadingControllerTest, StoreShouldStoreAssociatedWithUserGivenMeterReadingsAssociatedWithTheUser) {
97+
json body1 = R"({
98+
"smartMeterId": "smart-meter-0",
99+
"electricityReadings": [
100+
{
101+
"time": "2021-08-18T06:42:15.725202Z",
102+
"reading": 1
103+
}
104+
]
105+
})"_json;
106+
json body2 = R"({
107+
"smartMeterId": "smart-meter-1",
108+
"electricityReadings": [
109+
{
110+
"time": "2021-08-18T06:44:15.725202Z",
111+
"reading": 2
112+
}
113+
]
114+
})"_json;
115+
auto req1 = BuildRequest(http::verb::post, "/readings/store", body1);
116+
auto req2 = BuildRequest(http::verb::post, "/readings/store", body2);
117+
std::vector<std::string> queries;
118+
119+
controller.Store(req1, queries);
120+
controller.Store(req2, queries);
121+
122+
std::vector<ElectricityReading> expectedElectricityReadings = {
123+
{detail::fromRfc3339("2021-08-18T06:42:15.725202Z"), 1},
124+
};
125+
126+
EXPECT_THAT(meterReadingService.getReadings("smart-meter-0"), Eq(expectedElectricityReadings));
127+
}
128+
129+
TEST_F(MeterReadingControllerTest, ReadShouldReturnNotFoundGivenMeterIdThatIsNotRecognised) {
130+
http::request<http::string_body> req;
131+
std::vector<std::string> queries = {"smart-meter-0"};
132+
133+
auto response = controller.Read(req, queries);
134+
135+
EXPECT_THAT(response.result(), Eq(http::status::not_found));
136+
}

0 commit comments

Comments
 (0)