Files
radiantwave/tests/test_audio_downloader.cpp
NeilClack c3b3d6774a Warning screen, smarter license re-validation, version reporting
Warning screen
- New reusable WarningPage (word-wrapped message, ENTER acknowledges and
  continues to the screen you were about to enter).
- First use: pressing Play while the selected audio is still downloading.
  Dynamic Audio requires every file; a fixed track requires its own file.

License re-validation
- No longer re-verifies on every Home render. Checks now run only when the
  email/license key is entered or changed, on the manual option, at startup
  when already due, and hourly while valid.
- Invalid + unchanged credentials are never re-checked (persisted pair).
- Valid re-checks at most once per hour, anchored on a DB timestamp so an
  overdue check runs immediately after a power cycle.
- New system_kv keys track the checked pair, outcome, and last-check time.
- Added a System Settings row "Manually Validate License Key OK|Invalid"
  (OK green, Invalid red, label white) that runs a check immediately.

Portal / local integration
- License validation request now sends the app version as `appVersion`.
- The entered license key is mirrored to ~/.local/radiantwave/license.txt.

Misc
- "Please connect to WiFi" only shows when no WiFi is joined AND the internet
  is unreachable (fixes the NetworkManager-missing and Ethernet cases).
- Added unit tests for the audio-download warning, license scheduling policy,
  license request shape, and the new settings-menu entry.
2026-10-14 10:58:40 -05:00

220 lines
8.0 KiB
C++

#include "audio/audio_downloader.h"
#include <gtest/gtest.h>
#include <unistd.h>
#include <fstream>
#include <string>
#include <vector>
namespace radiantwave::audio {
namespace {
std::string tempPath(const std::string& suffix) {
return "/tmp/radiantwave_ad_test_" + std::to_string(static_cast<long>(getpid())) + suffix;
}
TEST(AudioDownloaderManifest, ParsesCurrentFormatWithDisplayName) {
const std::string text =
"# checksums generated by md5sum\n"
"174Hz.wav '174 Hz Solfeggio' 174 5d41402abc4b2a76b9719d911017c592\n"
"PinealGland.wav 'Pineal Gland Activator' 0 e99a18c428cb38d5f260853678922e03\n";
std::vector<AudioManifestEntry> manifest;
ASSERT_TRUE(parseManifest(text, manifest));
ASSERT_EQ(manifest.size(), 2u);
EXPECT_EQ(manifest[0].filename, "174Hz.wav");
EXPECT_EQ(manifest[0].display_name, "174 Hz Solfeggio");
EXPECT_EQ(manifest[0].solfeggio_frequency_hz, 174);
EXPECT_EQ(manifest[0].md5, "5d41402abc4b2a76b9719d911017c592");
EXPECT_EQ(manifest[1].filename, "PinealGland.wav");
EXPECT_EQ(manifest[1].display_name, "Pineal Gland Activator");
EXPECT_EQ(manifest[1].solfeggio_frequency_hz, 0);
EXPECT_EQ(manifest[1].md5, "e99a18c428cb38d5f260853678922e03");
}
TEST(AudioDownloaderManifest, ParsesZeroHzAsNull) {
const std::string text = "OriginalAudio.wav 'Original Audio' 0 5d41402abc4b2a76b9719d911017c592\n";
std::vector<AudioManifestEntry> manifest;
ASSERT_TRUE(parseManifest(text, manifest));
ASSERT_EQ(manifest.size(), 1u);
EXPECT_EQ(manifest[0].solfeggio_frequency_hz, 0);
}
TEST(AudioDownloaderManifest, FallsBackToFilenameForUnrecognizedHzToken) {
// A transitional manifest may still carry a frequency id (or any
// unrecognized number) in token 2; the filename Hz must win.
const std::string text =
"396Hz.wav '396 Hz' 2 5d41402abc4b2a76b9719d911017c592\n"
"1111Hz.wav '1111 Hz' 10 e99a18c428cb38d5f260853678922e03\n";
std::vector<AudioManifestEntry> manifest;
ASSERT_TRUE(parseManifest(text, manifest));
ASSERT_EQ(manifest.size(), 2u);
EXPECT_EQ(manifest[0].solfeggio_frequency_hz, 396);
EXPECT_EQ(manifest[1].solfeggio_frequency_hz, 1111);
}
TEST(AudioDownloaderManifest, ParsesLegacyMd5sumFormat) {
const std::string text =
"5d41402abc4b2a76b9719d911017c592 hello.txt\n"
"e99a18c428cb38d5f260853678922e03 *396Hz.wav\n"
"a9993e364706816aba3e25717850c26c ./nested.wav\n";
std::vector<AudioManifestEntry> manifest;
ASSERT_TRUE(parseManifest(text, manifest));
ASSERT_EQ(manifest.size(), 3u);
EXPECT_EQ(manifest[0].filename, "hello.txt");
EXPECT_EQ(manifest[0].display_name, "hello");
EXPECT_EQ(manifest[0].solfeggio_frequency_hz, 0);
EXPECT_EQ(manifest[0].md5, "5d41402abc4b2a76b9719d911017c592");
EXPECT_EQ(manifest[1].filename, "396Hz.wav");
EXPECT_EQ(manifest[1].solfeggio_frequency_hz, 396);
EXPECT_EQ(manifest[1].md5, "e99a18c428cb38d5f260853678922e03");
EXPECT_EQ(manifest[2].filename, "nested.wav");
EXPECT_EQ(manifest[2].solfeggio_frequency_hz, 0);
EXPECT_EQ(manifest[2].md5, "a9993e364706816aba3e25717850c26c");
}
TEST(AudioDownloaderManifest, SkipsMalformedLines) {
const std::string text =
"not-a-valid-line\n"
"5d41402abc4b2a76b9719d911017c592 ok.txt\n"
"1111Hz.wav 'Display Name With Spaces' 10 5d41402abc4b2a76b9719d911017c592\n";
std::vector<AudioManifestEntry> manifest;
ASSERT_TRUE(parseManifest(text, manifest));
ASSERT_EQ(manifest.size(), 2u);
EXPECT_EQ(manifest[0].filename, "ok.txt");
EXPECT_EQ(manifest[0].solfeggio_frequency_hz, 0);
EXPECT_EQ(manifest[1].filename, "1111Hz.wav");
EXPECT_EQ(manifest[1].display_name, "Display Name With Spaces");
EXPECT_EQ(manifest[1].solfeggio_frequency_hz, 1111);
}
TEST(AudioDownloaderManifest, EmptyOrCommentedTextFails) {
std::vector<AudioManifestEntry> manifest;
EXPECT_FALSE(parseManifest("", manifest));
EXPECT_FALSE(parseManifest("# only a comment\n", manifest));
}
TEST(AudioDownloaderFrequency, MapsKnownHzToIds) {
EXPECT_EQ(frequencyIdForHz(174), 1);
EXPECT_EQ(frequencyIdForHz(285), 1);
EXPECT_EQ(frequencyIdForHz(396), 2);
EXPECT_EQ(frequencyIdForHz(417), 3);
EXPECT_EQ(frequencyIdForHz(432), 4);
EXPECT_EQ(frequencyIdForHz(528), 5);
EXPECT_EQ(frequencyIdForHz(639), 6);
EXPECT_EQ(frequencyIdForHz(741), 7);
EXPECT_EQ(frequencyIdForHz(852), 8);
EXPECT_EQ(frequencyIdForHz(963), 9);
EXPECT_EQ(frequencyIdForHz(1111), 0);
EXPECT_EQ(frequencyIdForHz(0), 0);
EXPECT_EQ(frequencyIdForHz(-5), 0);
}
TEST(AudioDownloaderFrequency, ParsesHzFromFilenames) {
EXPECT_EQ(hzFromFilename("396Hz.wav"), 396);
EXPECT_EQ(hzFromFilename("14Hz_Binaural.wav"), 14);
EXPECT_EQ(hzFromFilename("1111Hz.wav"), 1111);
EXPECT_EQ(hzFromFilename("396hz.wav"), 396);
EXPECT_EQ(hzFromFilename("AlphaWaves.wav"), 0);
EXPECT_EQ(hzFromFilename("OriginalAudio.wav"), 0);
EXPECT_EQ(hzFromFilename(""), 0);
EXPECT_EQ(hzFromFilename("1x396Hz.wav"), 0);
}
TEST(AudioDownloaderMd5, ComputesKnownDigest) {
std::string path = tempPath("_hello.txt");
{
std::ofstream out(path, std::ios::binary);
out << "hello";
}
EXPECT_EQ(computeFileMd5(path), "5d41402abc4b2a76b9719d911017c592");
std::remove(path.c_str());
}
TEST(AudioDownloaderMd5, EmptyFileDigest) {
std::string path = tempPath("_empty.txt");
{
std::ofstream out(path, std::ios::binary);
}
EXPECT_EQ(computeFileMd5(path), "d41d8cd98f00b204e9800998ecf8427e");
std::remove(path.c_str());
}
TEST(AudioDownloaderMd5, MissingFileReturnsEmpty) {
EXPECT_EQ(computeFileMd5(tempPath("_missing.bin")), "");
}
TEST(AudioDownloadWarning, DynamicAudioWarnsWhenAnyFileMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"396Hz.wav", "396 Hz", false},
};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, true, "396Hz.wav", message));
EXPECT_NE(message.find("'Dynamic Audio'"), std::string::npos);
EXPECT_NE(message.find("these files"), std::string::npos);
}
TEST(AudioDownloadWarning, DynamicAudioSilentWhenAllAvailable) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = true;
snap.files = {{"174Hz.wav", "174 Hz", true}};
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, true, "174Hz.wav", message));
}
TEST(AudioDownloadWarning, FixedTrackWarnsWhenItsFileMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"OriginalAudio.wav", "Original Audio", false},
};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, false, "OriginalAudio.wav", message));
EXPECT_NE(message.find("'Original Audio'"), std::string::npos);
EXPECT_NE(message.find("this file"), std::string::npos);
}
TEST(AudioDownloadWarning, FixedTrackSilentWhenItsFilePresentEvenIfOthersMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"396Hz.wav", "396 Hz", false},
};
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, false, "174Hz.wav", message));
}
TEST(AudioDownloadWarning, UnknownSelectionNeverWarns) {
// No manifest fetched yet: the downloader has no evidence either way.
AudioDownloadSnapshot snap;
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, false, "OriginalAudio.wav", message));
}
TEST(AudioDownloadWarning, FallsBackToFilenameForDisplayName) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.files = {{"Mystery.wav", "", false}};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, false, "Mystery.wav", message));
EXPECT_NE(message.find("'Mystery.wav'"), std::string::npos);
}
} // namespace
} // namespace radiantwave::audio