Files
IQ.Pilot/iqpilot/tools/replay/tests/test_api.cc
2026-08-22 23:42:41 -05:00

73 lines
2.7 KiB
C++

#include "catch2/catch.hpp"
#include "tools/replay/api.h"
// These cover the konn3kt endpoints that replaced upstream's PyDownloader shell-outs.
// Cabana's remote-routes dialog and jotpluggler's route-file listing both route through
// here, so a silent change to a path or to the error envelope breaks konn3kt without
// breaking the build.
TEST_CASE("konn3kt api paths") {
SECTION("route files") {
REQUIRE(CommaApi2::routeFilesPath("a2a0ccea32023010|2023-07-27--13-01-19") ==
"v1/route/a2a0ccea32023010|2023-07-27--13-01-19/files");
}
SECTION("devices") {
REQUIRE(CommaApi2::devicesPath() == "v1/me/devices/");
}
SECTION("device routes over a time range") {
REQUIRE(CommaApi2::deviceRoutesPath("dongle", 1000, 2000, false) ==
"v1/devices/dongle/routes_segments?start=1000&end=2000");
}
SECTION("device routes with only one bound") {
REQUIRE(CommaApi2::deviceRoutesPath("dongle", 1000, 0, false) ==
"v1/devices/dongle/routes_segments?start=1000");
REQUIRE(CommaApi2::deviceRoutesPath("dongle", 0, 2000, false) ==
"v1/devices/dongle/routes_segments?end=2000");
}
SECTION("device routes with no bounds omits the query entirely") {
REQUIRE(CommaApi2::deviceRoutesPath("dongle", 0, 0, false) ==
"v1/devices/dongle/routes_segments");
}
SECTION("preserved routes ignore the time range") {
REQUIRE(CommaApi2::deviceRoutesPath("dongle", 1000, 2000, true) ==
"v1/devices/dongle/routes/preserved");
}
}
TEST_CASE("konn3kt api error envelope") {
// Cabana and jotpluggler both branch on these exact strings.
SECTION("success passes the body through") {
REQUIRE(CommaApi2::apiResponse("[{\"dongle_id\": \"abc\"}]", 200) == "[{\"dongle_id\": \"abc\"}]");
}
SECTION("401 and 403 both map to unauthorized") {
REQUIRE(CommaApi2::apiResponse("", 401) == R"({"error": "unauthorized"})");
REQUIRE(CommaApi2::apiResponse("nope", 403) == R"({"error": "unauthorized"})");
}
SECTION("404 maps to not_found") {
REQUIRE(CommaApi2::apiResponse("", 404) == R"({"error": "not_found"})");
}
SECTION("transport failure and 5xx map to network") {
REQUIRE(CommaApi2::apiResponse("", 0) == R"({"error": "network"})");
REQUIRE(CommaApi2::apiResponse("boom", 500) == R"({"error": "network"})");
}
SECTION("a 200 with an empty body is still an error") {
REQUIRE(CommaApi2::apiResponse("", 200) == R"({"error": "network"})");
}
}
TEST_CASE("konn3kt base url") {
// Everything is built off BASE_URL; an accidental revert to comma's host would
// silently point every tool at api.comma.ai.
REQUIRE(CommaApi2::BASE_URL.find("comma.ai") == std::string::npos);
}