initial commit
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
#include "network/api_client.h"
|
||||
#include "network/json.h"
|
||||
#include "network/proclamation_sync.h"
|
||||
#include "db/database_manager.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace radiantwave::api;
|
||||
|
||||
namespace {
|
||||
|
||||
// Reads an env var, returning "" when unset.
|
||||
std::string env(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
return value ? std::string(value) : std::string();
|
||||
}
|
||||
|
||||
// Returns the optional live API key, or "" when none was provided.
|
||||
std::string liveApiKey() {
|
||||
return env("RW_LIVE_API_KEY");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ─── Offline: parse functions ─────────────────────────────────────────
|
||||
|
||||
TEST(ApiClientTest, ParseFrequenciesJson) {
|
||||
const std::string body =
|
||||
R"([{"id":1,"name":"Crown","value":936},
|
||||
{"id":5,"name":"Solar Plexus","value":528},
|
||||
{"id":7,"name":"Root","value":396}])";
|
||||
|
||||
std::vector<Frequency> out;
|
||||
EXPECT_TRUE(parseFrequenciesJson(body, out));
|
||||
ASSERT_EQ(out.size(), 3u);
|
||||
|
||||
EXPECT_EQ(out[0].id, 1);
|
||||
EXPECT_EQ(out[0].name, "Crown");
|
||||
EXPECT_EQ(out[0].value, 936);
|
||||
|
||||
EXPECT_EQ(out[1].id, 5);
|
||||
EXPECT_EQ(out[1].name, "Solar Plexus");
|
||||
EXPECT_EQ(out[1].value, 528);
|
||||
|
||||
EXPECT_EQ(out[2].id, 7);
|
||||
EXPECT_EQ(out[2].name, "Root");
|
||||
EXPECT_EQ(out[2].value, 396);
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseFrequenciesJsonEmpty) {
|
||||
std::vector<Frequency> out;
|
||||
EXPECT_TRUE(parseFrequenciesJson("[]", out));
|
||||
EXPECT_TRUE(out.empty());
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseFrequenciesJsonRejectsMalformed) {
|
||||
std::vector<Frequency> out;
|
||||
EXPECT_FALSE(parseFrequenciesJson("not json", out));
|
||||
EXPECT_FALSE(parseFrequenciesJson(R"({"id":1})", out)); // object, not array
|
||||
EXPECT_FALSE(parseFrequenciesJson(R"([{"id":1}", ])", out));
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseFrequenciesJsonSkipsMissingId) {
|
||||
std::vector<Frequency> out;
|
||||
EXPECT_TRUE(parseFrequenciesJson(
|
||||
R"([{"name":"NoId"},{"id":3,"name":"Throat","value":741}])", out));
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0].id, 3);
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseLicenseJsonLegacyStringLevel) {
|
||||
const std::string body =
|
||||
R"({"key":"ABCD-EFGH-IJKL-MNOP","email_address":"user@example.com","subscription_level":"Basic"})";
|
||||
|
||||
LicenseInfo info;
|
||||
EXPECT_TRUE(parseLicenseJson(body, info));
|
||||
EXPECT_EQ(info.key, "ABCD-EFGH-IJKL-MNOP");
|
||||
EXPECT_EQ(info.email_address, "user@example.com");
|
||||
EXPECT_EQ(info.subscription_level.name, "Basic");
|
||||
EXPECT_EQ(info.subscription_level.id, 0);
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseLicenseJsonObjectLevel) {
|
||||
const std::string body =
|
||||
R"({"key":"ABCD-EFGH-IJKL-MNOP","email_address":"user@example.com",)"
|
||||
R"("subscription_level":{"id":2,"name":"Advanced","description":"General + Specific"}})";
|
||||
|
||||
LicenseInfo info;
|
||||
EXPECT_TRUE(parseLicenseJson(body, info));
|
||||
EXPECT_EQ(info.key, "ABCD-EFGH-IJKL-MNOP");
|
||||
EXPECT_EQ(info.email_address, "user@example.com");
|
||||
EXPECT_EQ(info.subscription_level.id, 2);
|
||||
EXPECT_EQ(info.subscription_level.name, "Advanced");
|
||||
EXPECT_EQ(info.subscription_level.description, "General + Specific");
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseLicenseJsonFlatLevel) {
|
||||
const std::string body =
|
||||
R"({"key":"ABCD-EFGH-IJKL-MNOP","email_address":"user@example.com",)"
|
||||
R"("subscription_level":"Elite","subscription_level_id":3,)"
|
||||
R"("subscription_level_description":"General + Specific + Precise"})";
|
||||
|
||||
LicenseInfo info;
|
||||
EXPECT_TRUE(parseLicenseJson(body, info));
|
||||
EXPECT_EQ(info.subscription_level.id, 3);
|
||||
EXPECT_EQ(info.subscription_level.name, "Elite");
|
||||
EXPECT_EQ(info.subscription_level.description, "General + Specific + Precise");
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseLicenseJsonRejectsMalformed) {
|
||||
LicenseInfo info;
|
||||
EXPECT_FALSE(parseLicenseJson("garbage", info));
|
||||
EXPECT_FALSE(parseLicenseJson(R"({"key":""})", info));
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseCreateAccountJson) {
|
||||
const std::string body =
|
||||
R"({"success":true,"message":"Account and license key created","user_id":"abc-123"})";
|
||||
|
||||
AccountBody account;
|
||||
EXPECT_TRUE(parseCreateAccountJson(body, account));
|
||||
EXPECT_TRUE(account.success);
|
||||
EXPECT_EQ(account.message, "Account and license key created");
|
||||
EXPECT_EQ(account.user_id, "abc-123");
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseCreateAccountJsonRejectsMalformed) {
|
||||
AccountBody account;
|
||||
EXPECT_FALSE(parseCreateAccountJson("nope", account));
|
||||
EXPECT_FALSE(parseCreateAccountJson("[]", account));
|
||||
}
|
||||
|
||||
// ─── Offline: error message extraction ────────────────────────────────
|
||||
|
||||
TEST(ApiClientTest, ExtractErrorMessage) {
|
||||
EXPECT_EQ(radiantwave::json::extractErrorMessage(R"({"message":"license key not found"})"),
|
||||
"license key not found");
|
||||
EXPECT_EQ(radiantwave::json::extractErrorMessage(R"({"error":"API key required"})"),
|
||||
"API key required");
|
||||
EXPECT_EQ(radiantwave::json::extractErrorMessage(R"({"code":400})"), "");
|
||||
EXPECT_EQ(radiantwave::json::extractErrorMessage("not json"), "");
|
||||
}
|
||||
|
||||
// ─── Offline: real-world payload shapes ────────────────────────────────
|
||||
|
||||
TEST(ApiClientTest, ParseProclamationsHandlesSubscriptionLevelIdNull) {
|
||||
const std::string body =
|
||||
R"([{"id":"a","name":"Peace","content":"<p>I am peace.</p>","categoryId":5,
|
||||
"solfeggioFrequencyId":5,"subscriptionLevelId":null},
|
||||
{"id":"b","name":"Foot","content":"<p>Heal.</p>","categoryId":32,
|
||||
"solfeggioFrequencyId":7,"subscriptionLevelId":1}])";
|
||||
|
||||
std::vector<radiantwave::db::ProclamationRecord> out;
|
||||
EXPECT_TRUE(radiantwave::sync::parseProclamationsJson(body, out));
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0].title, "Peace");
|
||||
EXPECT_EQ(out[0].category_id, 5);
|
||||
EXPECT_EQ(out[0].solfeggio_frequency_id, 5);
|
||||
EXPECT_EQ(out[1].category_id, 32);
|
||||
}
|
||||
|
||||
TEST(ApiClientTest, ParseCategoriesHandlesNullParentId) {
|
||||
const std::string body =
|
||||
R"([{"id":4,"name":"Spiritual","sortOrder":0,"parentId":null},
|
||||
{"id":5,"name":"General","sortOrder":0,"parentId":4}])";
|
||||
|
||||
std::vector<radiantwave::db::ProclamationCategoryRecord> out;
|
||||
EXPECT_TRUE(radiantwave::sync::parseCategoriesJson(body, out));
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0].parent_id, 0); // null parentId maps to 0 (root)
|
||||
EXPECT_EQ(out[1].parent_id, 4);
|
||||
}
|
||||
|
||||
// ─── Live: GET /frequencies (harmless, always runs) ───────────────────
|
||||
|
||||
TEST(ApiClientLiveTest, FetchFrequenciesLive) {
|
||||
std::vector<Frequency> out;
|
||||
EXPECT_TRUE(fetchFrequencies(out, liveApiKey()));
|
||||
ASSERT_GE(out.size(), 7u);
|
||||
|
||||
bool foundSolarPlexus = false;
|
||||
for (const Frequency& f : out) {
|
||||
if (f.id == 5) {
|
||||
foundSolarPlexus = true;
|
||||
EXPECT_EQ(f.name, "Solar Plexus");
|
||||
EXPECT_EQ(f.value, 528);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(foundSolarPlexus);
|
||||
}
|
||||
|
||||
// ─── Live: proclamation + category parsers against the real API ────────
|
||||
|
||||
TEST(ApiClientLiveTest, ProclamationsAndCategoriesLiveSmoke) {
|
||||
HttpResponse procsResponse = httpGet(PATH_PROCLAMATIONS, liveApiKey());
|
||||
ASSERT_TRUE(procsResponse.transportOk);
|
||||
ASSERT_EQ(procsResponse.status, 200);
|
||||
|
||||
std::vector<radiantwave::db::ProclamationRecord> proclamations;
|
||||
EXPECT_TRUE(radiantwave::sync::parseProclamationsJson(procsResponse.body, proclamations));
|
||||
EXPECT_GE(proclamations.size(), 50u);
|
||||
if (!proclamations.empty()) {
|
||||
// HTML tags must be stripped from the content.
|
||||
EXPECT_EQ(proclamations[0].content.find('<'), std::string::npos);
|
||||
EXPECT_FALSE(proclamations[0].id.empty());
|
||||
}
|
||||
|
||||
HttpResponse catsResponse = httpGet(PATH_CATEGORIES, liveApiKey());
|
||||
ASSERT_TRUE(catsResponse.transportOk);
|
||||
ASSERT_EQ(catsResponse.status, 200);
|
||||
|
||||
std::vector<radiantwave::db::ProclamationCategoryRecord> categories;
|
||||
EXPECT_TRUE(radiantwave::sync::parseCategoriesJson(catsResponse.body, categories));
|
||||
EXPECT_GE(categories.size(), 20u);
|
||||
}
|
||||
|
||||
// ─── Live: POST /license error paths (no credentials required) ─────────
|
||||
|
||||
TEST(ApiClientLiveTest, ValidateLicenseMissingKeyLive) {
|
||||
LicenseResult result = validateLicense("", "test@example.com", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_EQ(result.error_message, "license_key is required");
|
||||
}
|
||||
|
||||
TEST(ApiClientLiveTest, ValidateLicenseMissingEmailLive) {
|
||||
LicenseResult result = validateLicense("AAAA-AAAA-AAAA-AAAA", "", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_EQ(result.error_message, "email_address is required");
|
||||
}
|
||||
|
||||
TEST(ApiClientLiveTest, ValidateLicenseUnknownKeyLive) {
|
||||
LicenseResult result =
|
||||
validateLicense("AAAA-AAAA-AAAA-AAAA", "nobody@example.com", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_EQ(result.error_message, "license key not found");
|
||||
}
|
||||
|
||||
// ─── Live: POST /license success path (requires env credentials) ───────
|
||||
|
||||
TEST(ApiClientLiveTest, ValidateLicenseSuccessLive) {
|
||||
const std::string key = env("RW_LIVE_LICENSE_KEY");
|
||||
const std::string email = env("RW_LIVE_LICENSE_EMAIL");
|
||||
if (key.empty() || email.empty()) {
|
||||
GTEST_SKIP() << "RW_LIVE_LICENSE_KEY / RW_LIVE_LICENSE_EMAIL not set";
|
||||
}
|
||||
|
||||
LicenseResult result = validateLicense(key, email, liveApiKey());
|
||||
ASSERT_TRUE(result.ok()) << "status=" << result.status
|
||||
<< " error=" << result.error_message;
|
||||
EXPECT_EQ(result.info.key, key);
|
||||
EXPECT_EQ(result.info.email_address, email);
|
||||
EXPECT_FALSE(result.info.subscription_level.name.empty());
|
||||
}
|
||||
|
||||
// ─── Live: POST /automations/create/account error paths (harmless) ─────
|
||||
|
||||
TEST(ApiClientLiveTest, CreateAccountMissingEmailLive) {
|
||||
AccountResult result = createAccount("", "Basic", "", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_EQ(result.error_message, "email is required");
|
||||
}
|
||||
|
||||
TEST(ApiClientLiveTest, CreateAccountMissingLevelLive) {
|
||||
AccountResult result = createAccount("test@example.com", "", "", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_EQ(result.error_message, "subscription_level is required");
|
||||
}
|
||||
|
||||
TEST(ApiClientLiveTest, CreateAccountInvalidLevelLive) {
|
||||
AccountResult result =
|
||||
createAccount("test@example.com", "NoSuchLevel123", "", liveApiKey());
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.status, 400);
|
||||
EXPECT_NE(result.error_message.find("not found"), std::string::npos);
|
||||
}
|
||||
|
||||
// ─── Live: POST /automations/create/account success (creates a real
|
||||
// ─── account + emails the user; requires RW_LIVE_ACCOUNT_EMAIL) ────────
|
||||
|
||||
TEST(ApiClientLiveTest, CreateAccountSuccessLive) {
|
||||
const std::string email = env("RW_LIVE_ACCOUNT_EMAIL");
|
||||
if (email.empty()) {
|
||||
GTEST_SKIP() << "RW_LIVE_ACCOUNT_EMAIL not set";
|
||||
}
|
||||
|
||||
AccountResult result = createAccount(email, "Basic", "RadiantWave Test", liveApiKey());
|
||||
ASSERT_TRUE(result.ok()) << "status=" << result.status
|
||||
<< " error=" << result.error_message;
|
||||
EXPECT_TRUE(result.body.success);
|
||||
EXPECT_EQ(result.body.message, "Account and license key created");
|
||||
EXPECT_FALSE(result.body.user_id.empty());
|
||||
}
|
||||
Reference in New Issue
Block a user