Remove API key requirement

This commit is contained in:
NeilClack
2026-08-17 20:36:59 -05:00
parent 359ae905d7
commit 74245ee07d
16 changed files with 74 additions and 194 deletions
+1 -12
View File
@@ -4,7 +4,6 @@ on:
# Manual only for now. Select the branch to build when dispatching:
# main -> APP_VERSION is the current git tag
# dev -> APP_VERSION is the current commit hash
# Both branches bake the portal API key from secrets.
workflow_dispatch:
jobs:
@@ -44,28 +43,18 @@ jobs:
else
APP_VERSION="$(git rev-parse --short HEAD)"
fi
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_ENV"
echo "Branch: ${{ gitea.ref_name }} -> APP_VERSION=$APP_VERSION"
if [ -n "${{ secrets.RWT_API_KEY }}" ]; then
echo "API key: provided via secret RWT_API_KEY"
else
echo "API key: NOT set (build proceeds without an Authorization header)"
fi
# checks is keyed per-system (checks.<system>.default); nix build's
# #attr shorthand can't resolve it, so resolve the current system inline.
# --impure lets the flake read APP_VERSION/API_KEY via builtins.getEnv
# --impure lets the flake read APP_VERSION via builtins.getEnv
# (pure evals always get the package defaults). The offline test suite
# runs in the package's checkPhase.
- name: Build + offline test suite
env:
API_KEY: ${{ secrets.RWT_API_KEY }}
run: nix build -L ".#checks.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" --impure
- name: Live portal tests
if: ${{ secrets.RWT_LIVE_API_KEY != '' }}
env:
RWT_LIVE_API_KEY: ${{ secrets.RWT_LIVE_API_KEY }}
RWT_LIVE_ACCOUNT_EMAIL: ${{ secrets.RWT_LIVE_ACCOUNT_EMAIL }}
RWT_LIVE_LICENSE_EMAIL: ${{ secrets.RWT_LIVE_LICENSE_EMAIL }}
RWT_LIVE_LICENSE_KEY: ${{ secrets.RWT_LIVE_LICENSE_KEY }}
+6 -24
View File
@@ -33,23 +33,8 @@ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DAPP_VERSION="1.2.3"
The Nix package bakes `-DAPP_VERSION=<pkg version>` via `cmakeFlags` (`version`
defaults to `0.1.0` in `nix/package.nix`; the flake overrides it from the
`APP_VERSION` env var on impure evals). `nix develop`
sets `APP_VERSION=dev` and exports `API_KEY` from the ambient environment
(falling back to `RWT_LIVE_API_KEY`). CMake seeds a *fresh* configure from the
`APP_VERSION` / `API_KEY` env vars; `-D` wins on reconfigure.
The portal API key is also a build-time argument: `-DAPI_KEY="..."` (CMake) or
`apiKey` on `callPackage` (Nix). It is compiled into the binary as the
`Authorization: Bearer` token and defaults to empty (no auth header). Keep the
real value in CI secrets, never in source — rotate by rebuilding with a new
key.
Version and API key can be baked in together with a single configure:
```sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
-DAPP_VERSION="1.2.3" \
-DAPI_KEY="$API_KEY"
```
sets `APP_VERSION=dev`. CMake seeds a *fresh* configure from the
`APP_VERSION` env var; `-D` wins on reconfigure.
- The app resolves `assets/...` relative to its working directory — launch
`./build/radiantwave` from the repo root. (The Nix wrapper chdirs to
@@ -238,14 +223,11 @@ cached.
`checks` is keyed per-system, so the workflow resolves the current system
via `nix eval --impure --expr 'builtins.currentSystem'`). The build bakes
in a per-branch `APP_VERSION` — the current git tag on `main`, the current
commit hash on `dev` — plus the portal API key from the `RWT_API_KEY`
secret. The flake reads `APP_VERSION`/`API_KEY` from the environment via
commit hash on `dev`. The flake reads `APP_VERSION` from the environment via
`builtins.getEnv`, so the build must pass `--impure` (pure evals get the
package defaults). When the `RWT_LIVE_API_KEY` secret is set it also runs
`test_api_client` / `test_license_service` against the portal via
`nix develop` (outside the sandbox, like a dev machine). Those live tests
also need `RWT_LIVE_ACCOUNT_EMAIL`, `RWT_LIVE_LICENSE_EMAIL`, and
`RWT_LIVE_LICENSE_KEY`.
package defaults). The live portal tests run from `nix develop` when the
`RWT_LIVE_*` secrets are set and need `RWT_LIVE_ACCOUNT_EMAIL`,
`RWT_LIVE_LICENSE_EMAIL`, and `RWT_LIVE_LICENSE_KEY`.
- `test_assets` must pass before committing — it catches missing fonts/images
that break the app at runtime.
- `cmake --build build` builds everything; pass `--target <name>` to build one
-15
View File
@@ -18,20 +18,6 @@ else()
"Version string shown in the Home footer (override at configure time)")
endif()
# API bearer key baked into the binary and sent as `Authorization: Bearer <key>`
# on every portal request. Leave empty to send no auth header. Never commit the
# real value — inject it at CI build time from a secret, e.g.:
# cmake ... -DAPI_KEY="${{ secrets.PORTAL_API_KEY }}"
# The `nix develop` shell exports API_KEY from the ambient environment, which
# seeds a fresh configure; -D wins on reconfigure.
if(DEFINED ENV{API_KEY})
set(API_KEY "$ENV{API_KEY}" CACHE STRING
"Portal API key baked into the binary (leave empty to skip auth)")
else()
set(API_KEY "" CACHE STRING
"Portal API key baked into the binary (leave empty to skip auth)")
endif()
find_package(SDL3 REQUIRED)
find_package(SDL3_ttf REQUIRED)
find_package(SDL3_image REQUIRED)
@@ -90,7 +76,6 @@ add_executable(radiantwave
target_compile_definitions(radiantwave PRIVATE
APP_VERSION="${APP_VERSION}"
API_KEY="${API_KEY}"
)
target_include_directories(radiantwave PRIVATE src)
+11 -43
View File
@@ -136,45 +136,13 @@ If `APP_VERSION` is left unset the footer falls back to the project version
from `CMakeLists.txt` (`project(... VERSION ...)`); a build that defines no
version at all shows `unknown`. The Nix build sets `APP_VERSION` automatically
to the flake package version, so a plain `nix build` needs no extra flags.
With `--impure` the flake instead reads the `APP_VERSION` / `API_KEY`
environment variables — this is how CI injects the version and portal key
With `--impure` the flake instead reads the `APP_VERSION`
environment variable — this is how CI injects the version
(see the CI note under *Common Pitfalls*). Pure evals always get the package
defaults, because `builtins.getEnv` cannot see the environment in pure
evaluation mode.
#### Portal API key
Every portal request sends `Authorization: Bearer <key>`. The key is baked
into the binary at build time via the `API_KEY` CMake cache variable. It is
**optional** — when unset, requests go out without an auth header. Keep the
real value out of the source tree and inject it at build time from your CI
secret store so it never lands in `git`:
```sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DAPI_KEY="${{ secrets.PORTAL_API_KEY }}"
cmake --build build
```
Rotate the key by building with a new value and shipping the new binary. The
Nix package accepts the same value via `callPackage ./nix/package.nix {
apiKey = "…"; }`, or via the `API_KEY` environment variable with
`nix build --impure`; the flake itself never embeds one.
#### Version + API key in one configure
`APP_VERSION` and `API_KEY` are independent CMake cache variables, so pass
both — plus the build type — in a single configure:
```sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
-DAPP_VERSION="1.2.3" \
-DAPI_KEY="${{ secrets.PORTAL_API_KEY }}"
cmake --build build
```
`-D` values always win over the `APP_VERSION` / `API_KEY` environment
variables, which `nix develop` seeds from the ambient environment.
#### Version in one configure
### Building with Nix
@@ -187,14 +155,14 @@ nix build .#packages.x86_64-linux.default # needs a Linux machine/builders
nix build .#packages.aarch64-linux.default
```
To bake in a custom version and/or portal key, build impurely — the flake
reads `APP_VERSION` and `API_KEY` from the environment on impure evals:
To bake in a custom version, build impurely — the flake
reads `APP_VERSION` from the environment on impure evals:
```sh
APP_VERSION="1.2.3" API_KEY="$PORTAL_API_KEY" nix build --impure .#default
APP_VERSION="1.2.3" nix build --impure .#default
```
A pure `nix build` always uses the package defaults (`0.1.0`, no auth header)
A pure `nix build` always uses the package defaults (`0.1.0`)
even if those variables are set in your shell, because pure evaluation cannot
see the environment. CI uses `--impure` to inject them.
@@ -633,9 +601,9 @@ status to decide which page to show.
workflow is `.gitea/workflows/build.yml`. It is **manual-only**
(`workflow_dispatch`) — pick the branch when dispatching. `main` builds bake
the current git tag as `APP_VERSION`; `dev` builds bake the current commit
hash. Both pass the portal key from the `RWT_API_KEY` secret and build with
`nix build --impure` (which lets the flake's env injection work). The
offline test suite runs in the package's `checkPhase`; the live portal tests
run from `nix develop` when the `RWT_LIVE_*` secrets are set.
hash. Both build with `nix build --impure` (which lets the flake's env
injection work). The offline test suite runs in the package's `checkPhase`;
the live portal tests run from `nix develop` when the `RWT_LIVE_*` secrets
are set.
- Asset tests (`test_assets`) must pass before committing -- they catch missing
fonts and images that would break the app at runtime.
+13 -20
View File
@@ -34,18 +34,16 @@
};
in
{
# Version and API key are injected from the ambient environment on
# impure evals (CI sets APP_VERSION/API_KEY from the git ref and a
# secret). Pure evals (`nix flake check`, local `nix build`) can't
# see the environment, so builtins.getEnv returns "" and the
# package falls back to its 0.1.0 default / no auth header.
# Version is injected from the ambient environment on
# impure evals (CI sets APP_VERSION from the git ref). Pure evals
# (`nix flake check`, local `nix build`) can't see the
# environment, so builtins.getEnv returns "" and the package
# falls back to its 0.1.0 default.
packages.default =
let
envVersion = builtins.getEnv "APP_VERSION";
envApiKey = builtins.getEnv "API_KEY";
packageArgs =
{ apiKey = envApiKey; }
// (nixpkgs.lib.optionalAttrs (envVersion != "") { version = envVersion; });
nixpkgs.lib.optionalAttrs (envVersion != "") { version = envVersion; };
in
pkgs.callPackage ./nix/package.nix packageArgs;
@@ -53,22 +51,17 @@
inputsFrom = [ self.packages.${system}.default ];
packages = [ ];
env = {
# APP_VERSION and API_KEY are CMake cache variables with no
# default; on a fresh configure cmake seeds their cache from
# environment variables of the same name, so a plain
# `cmake -S . -B build` bakes them into the binary.
# APP_VERSION is a CMake cache variable with no default; on a
# fresh configure cmake seeds its cache from the environment
# variable of the same name, so a plain `cmake -S . -B build`
# bakes it into the binary.
APP_VERSION = "dev";
};
shellHook = ''
# Never commit the real key. Export API_KEY (or the CI name
# RWT_LIVE_API_KEY) in your ambient environment and this shell
# picks it up; empty by default (no auth header).
export API_KEY="''${API_KEY:-''${RWT_LIVE_API_KEY:-}}"
echo "RadiantWave dev shell (APP_VERSION=dev, API_KEY=$([[ -n "$API_KEY" ]] && echo set || echo unset))"
echo "RadiantWave dev shell (APP_VERSION=dev)"
echo " Configure: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug"
echo " Bake version+key: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DAPP_VERSION=\"1.2.3\" -DAPI_KEY=\"<key>\""
echo " Nix build (env version+key, impure): APP_VERSION=... API_KEY=... nix build --impure .#default"
echo " Bake version: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DAPP_VERSION=\"1.2.3\""
echo " Nix build (env version, impure): APP_VERSION=... nix build --impure .#default"
echo " Build: cmake --build build"
echo " Run: ./build/radiantwave"
echo " Tests: ./build/test_assets && ./build/test_<name>"
+1 -7
View File
@@ -15,9 +15,6 @@
curl,
openssl,
systemdLibs,
# Portal API key baked into the binary. Keep it out of the flake; pass it
# when calling callPackage from CI/your own config (see README "App version").
apiKey ? "",
# App version baked into the Home footer's Version row. CI overrides this
# per branch (git tag on main, commit hash on dev); local/pure builds keep
# the 0.1.0 default.
@@ -54,10 +51,7 @@ stdenv.mkDerivation (finalAttrs: {
# Bake the package version into the binary so the Home footer's Version row
# shows it (CMake passes APP_VERSION through as a compile definition).
# API_KEY is only added when a non-empty key is supplied to callPackage.
cmakeFlags =
[ "-DAPP_VERSION=${finalAttrs.version}" ]
++ lib.optionals (apiKey != "") [ "-DAPI_KEY=${apiKey}" ];
cmakeFlags = [ "-DAPP_VERSION=${finalAttrs.version}" ];
# gtest is a buildInput, not native: nixpkgs builds it shared, so the
# check-phase test binaries need its lib dir baked into their RPATH.
+1 -1
View File
@@ -97,7 +97,7 @@ void LicenseKeyPage::submit()
// When the email is missing the key is saved but the check is skipped.
std::string errorMessage;
const radiantwave::license::Result result = radiantwave::license::validateStoredLicense(
state_->db, "", &errorMessage);
state_->db, &errorMessage);
switch (result) {
case radiantwave::license::Result::Ok:
+13 -26
View File
@@ -4,10 +4,6 @@
#include <curl/curl.h>
#include <string>
#ifndef API_KEY
#define API_KEY ""
#endif
namespace radiantwave::api {
namespace {
@@ -31,7 +27,7 @@ size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
}
// Shared curl setup + perform. Returns a populated HttpResponse.
HttpResponse performRequest(const std::string& url, const std::string& apiKey,
HttpResponse performRequest(const std::string& url,
bool post, const std::string& jsonBody) {
HttpResponse response;
@@ -47,12 +43,6 @@ HttpResponse performRequest(const std::string& url, const std::string& apiKey,
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
struct curl_slist* headers = nullptr;
// An explicit apiKey wins; otherwise fall back to the key baked in at
// build time via -DAPI_KEY (empty string when unset).
const std::string effectiveKey = apiKey.empty() ? API_KEY : apiKey;
if (!effectiveKey.empty()) {
headers = curl_slist_append(headers, ("Authorization: Bearer " + effectiveKey).c_str());
}
if (post) {
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_POST, 1L);
@@ -79,13 +69,12 @@ HttpResponse performRequest(const std::string& url, const std::string& apiKey,
// ─── HTTP helpers ──────────────────────────────────────────────────────
HttpResponse httpGet(const std::string& path, const std::string& apiKey) {
return performRequest(urlFor(path), apiKey, false, "");
HttpResponse httpGet(const std::string& path) {
return performRequest(urlFor(path), false, "");
}
HttpResponse httpPost(const std::string& path, const std::string& jsonBody,
const std::string& apiKey) {
return performRequest(urlFor(path), apiKey, true, jsonBody);
HttpResponse httpPost(const std::string& path, const std::string& jsonBody) {
return performRequest(urlFor(path), true, jsonBody);
}
// ─── GET /frequencies ──────────────────────────────────────────────────
@@ -112,8 +101,8 @@ bool parseFrequenciesJson(const std::string& body, std::vector<Frequency>& out)
return true;
}
bool fetchFrequencies(std::vector<Frequency>& out, const std::string& apiKey) {
HttpResponse response = httpGet(PATH_FREQUENCIES, apiKey);
bool fetchFrequencies(std::vector<Frequency>& out) {
HttpResponse response = httpGet(PATH_FREQUENCIES);
if (!response.transportOk || response.status < 200 || response.status >= 300) {
return false;
}
@@ -169,8 +158,8 @@ bool parseColorPalettesJson(const std::string& body, std::vector<ColorPalette>&
return true;
}
bool fetchColorPalettes(std::vector<ColorPalette>& out, const std::string& apiKey) {
HttpResponse response = httpGet(PATH_COLOR_PALETTES, apiKey);
bool fetchColorPalettes(std::vector<ColorPalette>& out) {
HttpResponse response = httpGet(PATH_COLOR_PALETTES);
if (!response.transportOk || response.status < 200 || response.status >= 300) {
return false;
}
@@ -196,13 +185,12 @@ bool parseLicenseJson(const std::string& body, LicenseInfo& out) {
return !out.key.empty();
}
LicenseResult validateLicense(const std::string& key, const std::string& email,
const std::string& apiKey) {
LicenseResult validateLicense(const std::string& key, const std::string& email) {
LicenseResult result;
const std::string jsonBody =
"{\"license_key\":\"" + key + "\",\"email_address\":\"" + email + "\"}";
HttpResponse response = httpPost(PATH_LICENSE, jsonBody, apiKey);
HttpResponse response = httpPost(PATH_LICENSE, jsonBody);
result.status = response.status;
if (!response.transportOk) {
@@ -233,8 +221,7 @@ bool parseCreateAccountJson(const std::string& body, AccountBody& out) {
return true;
}
AccountResult createAccount(const std::string& email, const std::string& name,
const std::string& apiKey) {
AccountResult createAccount(const std::string& email, const std::string& name) {
AccountResult result;
std::string jsonBody =
@@ -244,7 +231,7 @@ AccountResult createAccount(const std::string& email, const std::string& name,
}
jsonBody += "}";
HttpResponse response = httpPost(PATH_CREATE_ACCOUNT, jsonBody, apiKey);
HttpResponse response = httpPost(PATH_CREATE_ACCOUNT, jsonBody);
result.status = response.status;
if (!response.transportOk) {
+7 -14
View File
@@ -24,14 +24,9 @@ struct HttpResponse {
bool transportOk = false; // true when the request completed without a transport error
};
// Performs a GET/POST against `path` (relative to API_BASE_URL). When `apiKey` is
// non-empty it is sent as `Authorization: Bearer <apiKey>`. When it is empty (or
// omitted) the request falls back to the key baked in at build time via the
// `API_KEY` compile definition (see CMakeLists.txt); with neither set, no auth
// header is sent.
HttpResponse httpGet(const std::string& path, const std::string& apiKey = "");
HttpResponse httpPost(const std::string& path, const std::string& jsonBody,
const std::string& apiKey = "");
// Performs a GET/POST against `path` (relative to API_BASE_URL).
HttpResponse httpGet(const std::string& path);
HttpResponse httpPost(const std::string& path, const std::string& jsonBody);
// ─── GET /frequencies ──────────────────────────────────────────────────
@@ -45,7 +40,7 @@ struct Frequency {
bool parseFrequenciesJson(const std::string& body, std::vector<Frequency>& out);
// Fetch the solfeggio frequencies. Returns true only on HTTP 2xx + successful parse.
bool fetchFrequencies(std::vector<Frequency>& out, const std::string& apiKey = "");
bool fetchFrequencies(std::vector<Frequency>& out);
// ─── GET /color-palettes ───────────────────────────────────────────────
@@ -75,7 +70,7 @@ bool parseColorPalettesJson(const std::string& body, std::vector<ColorPalette>&
// NOTE: the portal does not expose this route yet. This is readiness code — it
// is deliberately not called anywhere in the app until the route ships, so
// palettes can be updated from the portal rather than code releases.
bool fetchColorPalettes(std::vector<ColorPalette>& out, const std::string& apiKey = "");
bool fetchColorPalettes(std::vector<ColorPalette>& out);
// ─── POST /license ─────────────────────────────────────────────────────
@@ -98,8 +93,7 @@ struct LicenseResult {
// Validate a license key / email pair against the portal. Never throws; failures
// (network, HTTP error status) are reported through the returned LicenseResult.
// A 2xx response means the license is valid and the subscription is active.
LicenseResult validateLicense(const std::string& key, const std::string& email,
const std::string& apiKey = "");
LicenseResult validateLicense(const std::string& key, const std::string& email);
// ─── POST /automations/create/account ──────────────────────────────────
@@ -122,7 +116,6 @@ struct AccountResult {
// Creates (or reuses) a user account and issues a license key. The key is emailed
// to the user and is NOT part of the response. `name` may be empty.
AccountResult createAccount(const std::string& email, const std::string& name = "",
const std::string& apiKey = "");
AccountResult createAccount(const std::string& email, const std::string& name = "");
} // namespace radiantwave::api
+1 -2
View File
@@ -17,7 +17,6 @@ long long nowEpochSeconds() {
} // namespace
Result validateStoredLicense(radiantwave::db::DatabaseManager& db,
const std::string& apiKey,
std::string* outErrorMessage) {
if (outErrorMessage) outErrorMessage->clear();
@@ -28,7 +27,7 @@ Result validateStoredLicense(radiantwave::db::DatabaseManager& db,
}
const radiantwave::api::LicenseResult result =
radiantwave::api::validateLicense(key, email, apiKey);
radiantwave::api::validateLicense(key, email);
if (result.status == 0) {
if (outErrorMessage) *outErrorMessage = "network error";
-1
View File
@@ -26,7 +26,6 @@ constexpr long long GRACE_PERIOD_SECONDS = 7LL * 24 * 60 * 60;
//
// `outErrorMessage` is an optional out-param.
Result validateStoredLicense(radiantwave::db::DatabaseManager& db,
const std::string& apiKey = "",
std::string* outErrorMessage = nullptr);
// Records the current time as the "last verified" stamp of the license. Called
+4 -6
View File
@@ -190,10 +190,9 @@ bool parseProclamationPalettesJson(const std::string& body,
return true;
}
bool syncColorPalettesFromApi(radiantwave::db::DatabaseManager& db,
const std::string& apiKey) {
bool syncColorPalettesFromApi(radiantwave::db::DatabaseManager& db) {
std::vector<api::ColorPalette> palettes;
if (!api::fetchColorPalettes(palettes, apiKey)) {
if (!api::fetchColorPalettes(palettes)) {
return false;
}
@@ -211,9 +210,8 @@ bool syncColorPalettesFromApi(radiantwave::db::DatabaseManager& db,
return db.replaceColorPalettes(records);
}
bool syncProclamationPalettesFromApi(radiantwave::db::DatabaseManager& db,
const std::string& apiKey) {
api::HttpResponse response = api::httpGet(api::PATH_PROCLAMATIONS, apiKey);
bool syncProclamationPalettesFromApi(radiantwave::db::DatabaseManager& db) {
api::HttpResponse response = api::httpGet(api::PATH_PROCLAMATIONS);
if (!response.transportOk || response.status < 200 || response.status >= 300) {
return false;
}
+2 -4
View File
@@ -37,13 +37,11 @@ bool parseProclamationPalettesJson(const std::string& body,
// NOTE: not yet wired into the app flow — the portal does not expose the route
// yet. Readiness code: palettes should be updated from the portal, not code
// releases, so callers should treat this as a future sync step.
bool syncColorPalettesFromApi(radiantwave::db::DatabaseManager& db,
const std::string& apiKey = "");
bool syncColorPalettesFromApi(radiantwave::db::DatabaseManager& db);
// Alternative to syncColorPalettesFromApi: fetch proclamations and replace the
// local palette table using the palettes embedded in each proclamation.
// NOTE: not yet wired into the app flow; see syncColorPalettesFromApi.
bool syncProclamationPalettesFromApi(radiantwave::db::DatabaseManager& db,
const std::string& apiKey = "");
bool syncProclamationPalettesFromApi(radiantwave::db::DatabaseManager& db);
} // namespace radiantwave::sync
+9 -14
View File
@@ -19,11 +19,6 @@ std::string env(const char* 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 ─────────────────────────────────────────
@@ -163,7 +158,7 @@ TEST(ApiClientTest, ParseCategoriesHandlesNullParentId) {
TEST(ApiClientLiveTest, FetchFrequenciesLive) {
std::vector<Frequency> out;
EXPECT_TRUE(fetchFrequencies(out, liveApiKey()));
EXPECT_TRUE(fetchFrequencies(out));
ASSERT_GE(out.size(), 7u);
bool foundSolarPlexus = false;
@@ -180,7 +175,7 @@ TEST(ApiClientLiveTest, FetchFrequenciesLive) {
// ─── Live: proclamation + category parsers against the real API ────────
TEST(ApiClientLiveTest, ProclamationsAndCategoriesLiveSmoke) {
HttpResponse procsResponse = httpGet(PATH_PROCLAMATIONS, liveApiKey());
HttpResponse procsResponse = httpGet(PATH_PROCLAMATIONS);
ASSERT_TRUE(procsResponse.transportOk);
ASSERT_EQ(procsResponse.status, 200);
@@ -193,7 +188,7 @@ TEST(ApiClientLiveTest, ProclamationsAndCategoriesLiveSmoke) {
EXPECT_FALSE(proclamations[0].id.empty());
}
HttpResponse catsResponse = httpGet(PATH_CATEGORIES, liveApiKey());
HttpResponse catsResponse = httpGet(PATH_CATEGORIES);
ASSERT_TRUE(catsResponse.transportOk);
ASSERT_EQ(catsResponse.status, 200);
@@ -205,14 +200,14 @@ TEST(ApiClientLiveTest, ProclamationsAndCategoriesLiveSmoke) {
// ─── Live: POST /license error paths (no credentials required) ─────────
TEST(ApiClientLiveTest, ValidateLicenseMissingKeyLive) {
LicenseResult result = validateLicense("", "test@example.com", liveApiKey());
LicenseResult result = validateLicense("", "test@example.com");
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());
LicenseResult result = validateLicense("AAAA-AAAA-AAAA-AAAA", "");
ASSERT_FALSE(result.ok());
EXPECT_EQ(result.status, 400);
EXPECT_EQ(result.error_message, "email_address is required");
@@ -220,7 +215,7 @@ TEST(ApiClientLiveTest, ValidateLicenseMissingEmailLive) {
TEST(ApiClientLiveTest, ValidateLicenseUnknownKeyLive) {
LicenseResult result =
validateLicense("AAAA-AAAA-AAAA-AAAA", "nobody@example.com", liveApiKey());
validateLicense("AAAA-AAAA-AAAA-AAAA", "nobody@example.com");
ASSERT_FALSE(result.ok());
EXPECT_EQ(result.status, 400);
EXPECT_EQ(result.error_message, "license key not found");
@@ -235,7 +230,7 @@ TEST(ApiClientLiveTest, ValidateLicenseSuccessLive) {
GTEST_SKIP() << "RW_LIVE_LICENSE_KEY / RW_LIVE_LICENSE_EMAIL not set";
}
LicenseResult result = validateLicense(key, email, liveApiKey());
LicenseResult result = validateLicense(key, email);
ASSERT_TRUE(result.ok()) << "status=" << result.status
<< " error=" << result.error_message;
EXPECT_EQ(result.info.key, key);
@@ -245,7 +240,7 @@ TEST(ApiClientLiveTest, ValidateLicenseSuccessLive) {
// ─── Live: POST /automations/create/account error paths (harmless) ─────
TEST(ApiClientLiveTest, CreateAccountMissingEmailLive) {
AccountResult result = createAccount("", "", liveApiKey());
AccountResult result = createAccount("", "");
ASSERT_FALSE(result.ok());
EXPECT_EQ(result.status, 400);
EXPECT_EQ(result.error_message, "email is required");
@@ -260,7 +255,7 @@ TEST(ApiClientLiveTest, CreateAccountSuccessLive) {
GTEST_SKIP() << "RW_LIVE_ACCOUNT_EMAIL not set";
}
AccountResult result = createAccount(email, "RadiantWave Test", liveApiKey());
AccountResult result = createAccount(email, "RadiantWave Test");
ASSERT_TRUE(result.ok()) << "status=" << result.status
<< " error=" << result.error_message;
EXPECT_TRUE(result.body.success);
+1 -1
View File
@@ -175,7 +175,7 @@ TEST(ColorPalettesLiveTest, FetchColorPalettesLive) {
}
std::vector<ColorPalette> palettes;
ASSERT_TRUE(fetchColorPalettes(palettes, env("RW_LIVE_API_KEY")));
ASSERT_TRUE(fetchColorPalettes(palettes));
EXPECT_FALSE(palettes.empty());
for (const auto& p : palettes) {
EXPECT_GE(p.colors.size(), 1u);
+4 -4
View File
@@ -48,7 +48,7 @@ TEST(LicenseServiceTest, NotConfiguredWhenNoEmail) {
tmp.db.setKV(KV_LICENSE_KEY, "ABCD-EFGH-IJKL-MNOP");
std::string errorMessage = "stale";
EXPECT_EQ(validateStoredLicense(tmp.db, "", &errorMessage),
EXPECT_EQ(validateStoredLicense(tmp.db, &errorMessage),
Result::NotConfigured);
EXPECT_EQ(errorMessage, "");
}
@@ -57,7 +57,7 @@ TEST(LicenseServiceTest, NotConfiguredWhenNoLicense) {
TempDb tmp;
tmp.db.setKV(KV_EMAIL_ADDRESS, "user@example.com");
EXPECT_EQ(validateStoredLicense(tmp.db, "", nullptr),
EXPECT_EQ(validateStoredLicense(tmp.db, nullptr),
Result::NotConfigured);
}
@@ -117,7 +117,7 @@ TEST(LicenseServiceLiveTest, ValidatesAndGrantsSubscribedState) {
tmp.db.setKV(KV_EMAIL_ADDRESS, email);
std::string errorMessage;
const Result result = validateStoredLicense(tmp.db, "", &errorMessage);
const Result result = validateStoredLicense(tmp.db, &errorMessage);
EXPECT_EQ(result, Result::Ok) << "error=" << errorMessage;
// A successful validation is the "subscribed" flag; no subscription-level
@@ -134,7 +134,7 @@ TEST(LicenseServiceLiveTest, InvalidPairReportsInvalid) {
tmp.db.setKV(KV_EMAIL_ADDRESS, "nobody@example.com");
std::string errorMessage;
EXPECT_EQ(validateStoredLicense(tmp.db, "", &errorMessage),
EXPECT_EQ(validateStoredLicense(tmp.db, &errorMessage),
Result::Invalid);
EXPECT_FALSE(errorMessage.empty());
}