267 lines
6.6 KiB
C++
267 lines
6.6 KiB
C++
#include "goldfish.hpp"
|
|
#include <iostream>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <random>
|
|
#include <vector>
|
|
|
|
using namespace goldfish;
|
|
using namespace std;
|
|
|
|
class TestRequest : public IRequest
|
|
{
|
|
private:
|
|
HttpMethod method_;
|
|
string_view path_;
|
|
string_view query_;
|
|
string_view body_;
|
|
|
|
public:
|
|
TestRequest(HttpMethod method, string_view path, string_view query = "", string_view body = "")
|
|
: method_(method), path_(path), query_(query), body_(body) {}
|
|
|
|
HttpMethod method() const override { return method_; }
|
|
string_view path() const override { return path_; }
|
|
string_view query() const override { return query_; }
|
|
string_view body() const override { return body_; }
|
|
string_view get_header(string_view) const override { return {}; }
|
|
};
|
|
|
|
class TestResponse : public IResponse
|
|
{
|
|
private:
|
|
int status_ = 200;
|
|
string body_;
|
|
string content_type_ = "text/plain";
|
|
|
|
public:
|
|
void set_status(int status) override { status_ = status; }
|
|
void set_header(string_view, string_view) override {}
|
|
void set_body(string_view body) override { body_ = string(body); }
|
|
void set_content_type(string_view content_type) override { content_type_ = string(content_type); }
|
|
|
|
int status() const { return status_; }
|
|
const string& body() const { return body_; }
|
|
};
|
|
|
|
void test_basic_routing()
|
|
{
|
|
Router router;
|
|
bool handler_called = false;
|
|
|
|
router.get("/test", [&](IRequest&, IResponse& res, Params) {
|
|
handler_called = true;
|
|
res.set_body("success");
|
|
});
|
|
|
|
TestRequest req(HttpMethod::GET, "/test");
|
|
TestResponse res;
|
|
|
|
assert(router.handle(req, res));
|
|
assert(handler_called);
|
|
assert(res.body() == "success");
|
|
cout << "✓ Basic routing test passed\n";
|
|
}
|
|
|
|
void test_parameter_extraction()
|
|
{
|
|
Router router;
|
|
string extracted_id;
|
|
|
|
router.get("/users/:id", [&](IRequest&, IResponse& res, Params params) {
|
|
extracted_id = string(params[0]);
|
|
res.set_body("user found");
|
|
});
|
|
|
|
TestRequest req(HttpMethod::GET, "/users/123");
|
|
TestResponse res;
|
|
|
|
assert(router.handle(req, res));
|
|
assert(extracted_id == "123");
|
|
cout << "✓ Parameter extraction test passed\n";
|
|
}
|
|
|
|
void test_multiple_parameters()
|
|
{
|
|
Router router;
|
|
string user_id, post_id;
|
|
|
|
router.get("/users/:userId/posts/:postId", [&](IRequest&, IResponse&, Params params) {
|
|
user_id = string(params[0]);
|
|
post_id = string(params[1]);
|
|
});
|
|
|
|
TestRequest req(HttpMethod::GET, "/users/456/posts/789");
|
|
TestResponse res;
|
|
|
|
assert(router.handle(req, res));
|
|
assert(user_id == "456");
|
|
assert(post_id == "789");
|
|
cout << "✓ Multiple parameters test passed\n";
|
|
}
|
|
|
|
void test_method_routing()
|
|
{
|
|
Router router;
|
|
string method_called;
|
|
|
|
router.get("/api", [&](IRequest&, IResponse&, Params) {
|
|
method_called = "GET";
|
|
});
|
|
|
|
router.post("/api", [&](IRequest&, IResponse&, Params) {
|
|
method_called = "POST";
|
|
});
|
|
|
|
TestRequest get_req(HttpMethod::GET, "/api");
|
|
TestRequest post_req(HttpMethod::POST, "/api");
|
|
TestResponse res;
|
|
|
|
assert(router.handle(get_req, res));
|
|
assert(method_called == "GET");
|
|
|
|
assert(router.handle(post_req, res));
|
|
assert(method_called == "POST");
|
|
cout << "✓ Method routing test passed\n";
|
|
}
|
|
|
|
void test_no_match()
|
|
{
|
|
Router router;
|
|
|
|
router.get("/existing", [](IRequest&, IResponse&, Params) {});
|
|
|
|
TestRequest req(HttpMethod::GET, "/nonexistent");
|
|
TestResponse res;
|
|
|
|
assert(!router.handle(req, res));
|
|
cout << "✓ No match test passed\n";
|
|
}
|
|
|
|
void test_mixed_static_and_dynamic()
|
|
{
|
|
Router router;
|
|
string result;
|
|
|
|
router.get("/api/users", [&](IRequest&, IResponse&, Params) {
|
|
result = "static";
|
|
});
|
|
|
|
router.get("/api/:resource", [&](IRequest&, IResponse&, Params params) {
|
|
result = "dynamic:" + string(params[0]);
|
|
});
|
|
|
|
TestRequest static_req(HttpMethod::GET, "/api/users");
|
|
TestRequest dynamic_req(HttpMethod::GET, "/api/posts");
|
|
TestResponse res;
|
|
|
|
assert(router.handle(static_req, res));
|
|
assert(result == "static");
|
|
|
|
assert(router.handle(dynamic_req, res));
|
|
assert(result == "dynamic:posts");
|
|
cout << "✓ Mixed static/dynamic routing test passed\n";
|
|
}
|
|
|
|
void benchmark_routing()
|
|
{
|
|
cout << "\nBenchmarking router performance...\n";
|
|
|
|
Router router;
|
|
vector<string> test_paths;
|
|
mt19937 rng(42);
|
|
uniform_int_distribution<> path_len(2, 6);
|
|
uniform_int_distribution<> segment_len(3, 12);
|
|
uniform_int_distribution<> param_chance(1, 4);
|
|
|
|
const int num_routes = 10000;
|
|
const int num_lookups = 1000000;
|
|
|
|
// Generate random routes
|
|
for (int i = 0; i < num_routes; ++i) {
|
|
string path = "/";
|
|
int segments = path_len(rng);
|
|
|
|
for (int j = 0; j < segments; ++j) {
|
|
if (j > 0) path += "/";
|
|
|
|
if (param_chance(rng) == 1) {
|
|
path += ":param" + std::to_string(j);
|
|
} else {
|
|
int len = segment_len(rng);
|
|
for (int k = 0; k < len; ++k) {
|
|
path += 'a' + (rng() % 26);
|
|
}
|
|
}
|
|
}
|
|
|
|
router.get(path, [](IRequest&, IResponse&, Params) {});
|
|
test_paths.push_back(path);
|
|
}
|
|
|
|
// Create lookup paths with realistic hit/miss ratio
|
|
vector<string> lookup_paths;
|
|
uniform_int_distribution<> hit_chance(1, 3);
|
|
|
|
for (int i = 0; i < num_lookups; ++i) {
|
|
if (hit_chance(rng) == 1 && !test_paths.empty()) {
|
|
string path = test_paths[rng() % test_paths.size()];
|
|
size_t pos = 0;
|
|
while ((pos = path.find(":param", pos)) != string::npos) {
|
|
size_t end = path.find("/", pos);
|
|
if (end == string::npos) end = path.length();
|
|
path.replace(pos, end - pos, std::to_string(rng() % 1000));
|
|
pos += 3;
|
|
}
|
|
lookup_paths.push_back(path);
|
|
} else {
|
|
string path = "/missing/" + std::to_string(rng() % 10000);
|
|
lookup_paths.push_back(path);
|
|
}
|
|
}
|
|
|
|
// Benchmark lookups
|
|
int successful_routes = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (const auto& path : lookup_paths) {
|
|
TestRequest req(HttpMethod::GET, path);
|
|
TestResponse res;
|
|
if (router.handle(req, res)) {
|
|
successful_routes++;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
|
|
double avg_lookup_time = double(duration.count()) / num_lookups;
|
|
double lookups_per_second = 1000000.0 / avg_lookup_time;
|
|
|
|
cout << "Routes registered: " << num_routes << "\n";
|
|
cout << "Lookups performed: " << num_lookups << "\n";
|
|
cout << "Successful routes: " << successful_routes << " ("
|
|
<< (100.0 * successful_routes / num_lookups) << "%)\n";
|
|
cout << "Total time: " << duration.count() << " μs\n";
|
|
cout << "Average lookup: " << avg_lookup_time << " μs\n";
|
|
cout << "Lookups/second: " << int(lookups_per_second) << "\n";
|
|
}
|
|
|
|
int main()
|
|
{
|
|
cout << "Running Goldfish Router Tests...\n\n";
|
|
|
|
test_basic_routing();
|
|
test_parameter_extraction();
|
|
test_multiple_parameters();
|
|
test_method_routing();
|
|
test_no_match();
|
|
test_mixed_static_and_dynamic();
|
|
|
|
cout << "\n✅ All tests passed!\n";
|
|
|
|
benchmark_routing();
|
|
|
|
return 0;
|
|
}
|