mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-02-07 00:46:19 +01:00
feat(api): add application image endpoint (#4627)
Some checks failed
CI / Release Setup (push) Has been cancelled
CodeQL / CodeQL (push) Has been cancelled
CI / GitHub Env Debug (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Docker (push) Has been cancelled
CI / FreeBSD (push) Has been cancelled
CI / Homebrew (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Archlinux (push) Has been cancelled
CI / Linux Copr (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Bundle Analysis (push) Has been cancelled
CI / Coverage-Homebrew-macos-14 (push) Has been cancelled
CI / Coverage-Homebrew-macos-15 (push) Has been cancelled
CI / Coverage-Homebrew-macos-26 (push) Has been cancelled
CI / Coverage-Archlinux (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-aarch64 (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-amd64 (push) Has been cancelled
CI / Coverage-Homebrew-ubuntu-22.04 (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
CI / Coverage-Linux-AppImage (push) Has been cancelled
CI / Coverage-Windows-AMD64 (push) Has been cancelled
CI / Release (push) Has been cancelled
CI / Release Homebrew Beta (push) Has been cancelled
Some checks failed
CI / Release Setup (push) Has been cancelled
CodeQL / CodeQL (push) Has been cancelled
CI / GitHub Env Debug (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Docker (push) Has been cancelled
CI / FreeBSD (push) Has been cancelled
CI / Homebrew (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Archlinux (push) Has been cancelled
CI / Linux Copr (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Bundle Analysis (push) Has been cancelled
CI / Coverage-Homebrew-macos-14 (push) Has been cancelled
CI / Coverage-Homebrew-macos-15 (push) Has been cancelled
CI / Coverage-Homebrew-macos-26 (push) Has been cancelled
CI / Coverage-Archlinux (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-aarch64 (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-amd64 (push) Has been cancelled
CI / Coverage-Homebrew-ubuntu-22.04 (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
CI / Coverage-Linux-AppImage (push) Has been cancelled
CI / Coverage-Windows-AMD64 (push) Has been cancelled
CI / Release (push) Has been cancelled
CI / Release Homebrew Beta (push) Has been cancelled
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2141917d71
commit
76b3a8596f
@@ -39,6 +39,9 @@ basic authentication with the admin username and password.
|
||||
## POST /api/config
|
||||
@copydoc confighttp::saveConfig()
|
||||
|
||||
## GET /api/covers/{index}
|
||||
@copydoc confighttp::getCover()
|
||||
|
||||
## POST /api/covers/upload
|
||||
@copydoc confighttp::uploadCover()
|
||||
|
||||
|
||||
@@ -191,13 +191,14 @@ namespace confighttp {
|
||||
* @brief Send a 404 Not Found response.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @param error_message The error message to include in the response.
|
||||
*/
|
||||
void not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
|
||||
void not_found(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Not Found") {
|
||||
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
|
||||
|
||||
nlohmann::json tree;
|
||||
tree["status_code"] = code;
|
||||
tree["error"] = "Not Found";
|
||||
tree["error"] = error_message;
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
@@ -262,6 +263,28 @@ namespace confighttp {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates the application index and sends error response if invalid.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @param index The application index/id.
|
||||
*/
|
||||
bool check_app_index(resp_https_t response, req_https_t request, int index) {
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
if (const auto &apps = file_tree["apps"]; index < 0 || index >= static_cast<int>(apps.size())) {
|
||||
std::string error;
|
||||
if (const int max_index = static_cast<int>(apps.size()) - 1; max_index < 0) {
|
||||
error = "No applications found";
|
||||
} else {
|
||||
error = std::format("'index' {} out of range, max index is {}", index, max_index);
|
||||
}
|
||||
bad_request(std::move(response), std::move(request), error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the index page.
|
||||
* @param response The HTTP response object.
|
||||
@@ -711,25 +734,19 @@ namespace confighttp {
|
||||
try {
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json new_apps = nlohmann::json::array();
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
auto &apps_node = file_tree["apps"];
|
||||
const int index = std::stoi(request->path_match[1]);
|
||||
|
||||
if (index < 0 || index >= static_cast<int>(apps_node.size())) {
|
||||
std::string error;
|
||||
if (const int max_index = static_cast<int>(apps_node.size()) - 1; max_index < 0) {
|
||||
error = "No applications to delete";
|
||||
} else {
|
||||
error = std::format("'index' {} out of range, max index is {}", index, max_index);
|
||||
}
|
||||
bad_request(response, request, error);
|
||||
if (!check_app_index(response, request, index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < apps_node.size(); ++i) {
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
auto &apps = file_tree["apps"];
|
||||
|
||||
for (size_t i = 0; i < apps.size(); ++i) {
|
||||
if (i != index) {
|
||||
new_apps.push_back(apps_node[i]);
|
||||
new_apps.push_back(apps[i]);
|
||||
}
|
||||
}
|
||||
file_tree["apps"] = new_apps;
|
||||
@@ -928,6 +945,67 @@ namespace confighttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get an application's image.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @note{The index in the url path is the application index.}
|
||||
*
|
||||
* @api_examples{/api/covers/9999 | GET| null}
|
||||
*/
|
||||
void getCover(resp_https_t response, req_https_t request) {
|
||||
if (!check_content_type(response, request, "application/json")) {
|
||||
return;
|
||||
}
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
try {
|
||||
const int index = std::stoi(request->path_match[1]);
|
||||
if (!check_app_index(response, request, index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
auto &apps = file_tree["apps"];
|
||||
|
||||
auto &app = apps[index];
|
||||
|
||||
// Get the image path from the app configuration
|
||||
std::string app_image_path;
|
||||
if (app.contains("image-path") && !app["image-path"].is_null()) {
|
||||
app_image_path = app["image-path"];
|
||||
}
|
||||
|
||||
// Use validate_app_image_path to resolve and validate the path
|
||||
// This handles extension validation, PNG signature validation, and path resolution
|
||||
std::string validated_path = proc::validate_app_image_path(app_image_path);
|
||||
|
||||
// Open and stream the validated file
|
||||
std::ifstream in(validated_path, std::ios::binary);
|
||||
if (!in) {
|
||||
BOOST_LOG(warning) << "Unable to read cover image file: " << validated_path;
|
||||
bad_request(response, request, "Unable to read cover image file");
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "image/png");
|
||||
headers.emplace("X-Frame-Options", "DENY");
|
||||
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "GetCover: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Upload a cover image.
|
||||
* @param response The HTTP response object.
|
||||
@@ -1324,7 +1402,9 @@ namespace confighttp {
|
||||
server.default_resource["PUT"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
server.default_resource["GET"] = not_found;
|
||||
server.default_resource["GET"] = [](resp_https_t response, req_https_t request) {
|
||||
not_found(response, request);
|
||||
};
|
||||
server.resource["^/$"]["GET"] = getIndexPage;
|
||||
server.resource["^/pin/?$"]["GET"] = getPinPage;
|
||||
server.resource["^/apps/?$"]["GET"] = getAppsPage;
|
||||
@@ -1351,6 +1431,7 @@ namespace confighttp {
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
server.resource["^/api/covers/([0-9]+)$"]["GET"] = getCover;
|
||||
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
|
||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
server.resource["^/assets\\/.+$"]["GET"] = getNodeModules;
|
||||
|
||||
@@ -39,8 +39,6 @@
|
||||
#include <share.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png"
|
||||
|
||||
namespace proc {
|
||||
using namespace std::literals;
|
||||
namespace pt = boost::property_tree;
|
||||
@@ -466,6 +464,40 @@ namespace proc {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates a path whether it is a valid PNG.
|
||||
* @param path The path to the PNG file.
|
||||
* @return true if the file has a valid PNG signature, false otherwise.
|
||||
*/
|
||||
bool check_valid_png(const std::filesystem::path &path) {
|
||||
// PNG signature as defined in PNG specification
|
||||
// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
|
||||
static constexpr std::array<unsigned char, 8> PNG_SIGNATURE = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A
|
||||
};
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<unsigned char, 8> header;
|
||||
file.read(reinterpret_cast<char *>(header.data()), 8);
|
||||
|
||||
if (file.gcount() != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return header == PNG_SIGNATURE;
|
||||
}
|
||||
|
||||
std::string validate_app_image_path(std::string app_image_path) {
|
||||
if (app_image_path.empty()) {
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
@@ -475,28 +507,39 @@ namespace proc {
|
||||
auto image_extension = std::filesystem::path(app_image_path).extension().string();
|
||||
boost::to_lower(image_extension);
|
||||
|
||||
// return the default box image if extension is not "png"
|
||||
// return the default box image if the extension is not "png"
|
||||
if (image_extension != ".png") {
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// check if image is in assets directory
|
||||
auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path;
|
||||
if (std::filesystem::exists(full_image_path)) {
|
||||
if (auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path; std::filesystem::exists(full_image_path)) {
|
||||
// Validate PNG signature
|
||||
if (!check_valid_png(full_image_path)) {
|
||||
BOOST_LOG(warning) << "Invalid PNG file at path ["sv << full_image_path << ']';
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
return full_image_path.string();
|
||||
} else if (app_image_path == "./assets/steam.png") {
|
||||
}
|
||||
|
||||
if (app_image_path == "./assets/steam.png") {
|
||||
// handle old default steam image definition
|
||||
return SUNSHINE_ASSETS_DIR "/steam.png";
|
||||
}
|
||||
|
||||
// check if specified image exists
|
||||
std::error_code code;
|
||||
if (!std::filesystem::exists(app_image_path, code)) {
|
||||
if (std::error_code code; !std::filesystem::exists(app_image_path, code)) {
|
||||
// return default box image if image does not exist
|
||||
BOOST_LOG(warning) << "Couldn't find app image at path ["sv << app_image_path << ']';
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// Validate PNG signature
|
||||
if (!check_valid_png(app_image_path)) {
|
||||
BOOST_LOG(warning) << "Invalid PNG file at path ["sv << app_image_path << ']';
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// image is a png, and not in assets directory
|
||||
// return only "content-type" http header compatible image type
|
||||
return app_image_path;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png"
|
||||
|
||||
namespace proc {
|
||||
using file_t = util::safe_ptr_v2<FILE, int, fclose>;
|
||||
|
||||
@@ -120,6 +122,7 @@ namespace proc {
|
||||
*/
|
||||
std::tuple<std::string, std::string> calculate_app_id(const std::string &app_name, std::string app_image_path, int index);
|
||||
|
||||
bool check_valid_png(const std::filesystem::path &path);
|
||||
std::string validate_app_image_path(std::string app_image_path);
|
||||
void refresh(const std::string &file_name);
|
||||
std::optional<proc::proc_t> parse(const std::string &file_name);
|
||||
|
||||
272
tests/unit/test_process.cpp
Normal file
272
tests/unit/test_process.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* @file tests/unit/test_process.cpp
|
||||
* @brief Test src/process.* functions.
|
||||
*/
|
||||
// test imports
|
||||
#include "../tests_common.h"
|
||||
|
||||
// standard imports
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
// local imports
|
||||
#include <src/process.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class ProcessPNGTest: public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create test directory
|
||||
test_dir = fs::temp_directory_path() / "sunshine_process_png_test";
|
||||
fs::create_directories(test_dir);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test directory
|
||||
if (fs::exists(test_dir)) {
|
||||
fs::remove_all(test_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a file with specific content
|
||||
void createTestFile(const fs::path &path, const std::vector<unsigned char> &content) const {
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char *>(content.data()), content.size());
|
||||
file.close();
|
||||
}
|
||||
|
||||
fs::path test_dir;
|
||||
};
|
||||
|
||||
// Tests for check_valid_png function
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_ValidSignature) {
|
||||
// Valid PNG signature
|
||||
const std::vector<unsigned char> valid_png_data = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A, // PNG signature
|
||||
// Add some dummy data to make it more realistic
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "valid.png";
|
||||
createTestFile(test_file, valid_png_data);
|
||||
|
||||
EXPECT_TRUE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_WrongSignature) {
|
||||
// Invalid PNG signature (wrong magic bytes)
|
||||
const std::vector<unsigned char> invalid_png_data = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "invalid.png";
|
||||
createTestFile(test_file, invalid_png_data);
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_TooShort) {
|
||||
// File too short (less than 8 bytes)
|
||||
const std::vector<unsigned char> short_data = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "short.png";
|
||||
createTestFile(test_file, short_data);
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_EmptyFile) {
|
||||
// Empty file
|
||||
const std::vector<unsigned char> empty_data = {};
|
||||
|
||||
const fs::path test_file = test_dir / "empty.png";
|
||||
createTestFile(test_file, empty_data);
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_NonExistentFile) {
|
||||
// File doesn't exist
|
||||
const fs::path test_file = test_dir / "nonexistent.png";
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_RealFile) {
|
||||
// Test with the actual sunshine.png from the project root
|
||||
|
||||
// Only run this test if the file exists
|
||||
if (const fs::path sunshine_png = fs::path(SUNSHINE_SOURCE_DIR) / "sunshine.png"; fs::exists(sunshine_png)) {
|
||||
EXPECT_TRUE(proc::check_valid_png(sunshine_png));
|
||||
} else {
|
||||
GTEST_SKIP() << "sunshine.png not found in project root";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_JPEGFile) {
|
||||
// JPEG signature (not PNG)
|
||||
const std::vector<unsigned char> jpeg_data = {
|
||||
0xFF,
|
||||
0xD8,
|
||||
0xFF,
|
||||
0xE0,
|
||||
0x00,
|
||||
0x10,
|
||||
0x4A,
|
||||
0x46
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "fake.png";
|
||||
createTestFile(test_file, jpeg_data);
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, CheckValidPNG_PartialSignature) {
|
||||
// Partial PNG signature (first 4 bytes correct, rest wrong)
|
||||
const std::vector<unsigned char> partial_png_data = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "partial.png";
|
||||
createTestFile(test_file, partial_png_data);
|
||||
|
||||
EXPECT_FALSE(proc::check_valid_png(test_file));
|
||||
}
|
||||
|
||||
// Tests for validate_app_image_path function
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_EmptyPath) {
|
||||
// Empty path should return default
|
||||
const std::string result = proc::validate_app_image_path("");
|
||||
EXPECT_EQ(result, DEFAULT_APP_IMAGE_PATH);
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_NonPNGExtension) {
|
||||
// Non-PNG extension should return default
|
||||
const std::string result = proc::validate_app_image_path("image.jpg");
|
||||
EXPECT_EQ(result, DEFAULT_APP_IMAGE_PATH);
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_CaseInsensitiveExtension) {
|
||||
// Test that .PNG (uppercase) is recognized
|
||||
// Create a valid PNG file
|
||||
const std::vector<unsigned char> valid_png_data = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "test.PNG";
|
||||
createTestFile(test_file, valid_png_data);
|
||||
|
||||
const std::string result = proc::validate_app_image_path(test_file.string());
|
||||
// Should accept uppercase .PNG extension
|
||||
EXPECT_NE(result, DEFAULT_APP_IMAGE_PATH);
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_NonExistentFile) {
|
||||
// Non-existent PNG file should return default
|
||||
const std::string result = proc::validate_app_image_path("/nonexistent/path/image.png");
|
||||
EXPECT_EQ(result, DEFAULT_APP_IMAGE_PATH);
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_InvalidPNGSignature) {
|
||||
// File with .png extension but invalid signature should return default
|
||||
const std::vector<unsigned char> invalid_data = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "invalid.png";
|
||||
createTestFile(test_file, invalid_data);
|
||||
|
||||
const std::string result = proc::validate_app_image_path(test_file.string());
|
||||
EXPECT_EQ(result, DEFAULT_APP_IMAGE_PATH);
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_ValidPNG) {
|
||||
// Valid PNG file should return the path
|
||||
const std::vector<unsigned char> valid_png_data = {
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52
|
||||
};
|
||||
|
||||
const fs::path test_file = test_dir / "valid.png";
|
||||
createTestFile(test_file, valid_png_data);
|
||||
|
||||
const std::string result = proc::validate_app_image_path(test_file.string());
|
||||
EXPECT_EQ(result, test_file.string());
|
||||
}
|
||||
|
||||
TEST_F(ProcessPNGTest, ValidateAppImagePath_OldSteamDefault) {
|
||||
// Test the special case for old steam image path
|
||||
const std::string result = proc::validate_app_image_path("./assets/steam.png");
|
||||
EXPECT_EQ(result, SUNSHINE_ASSETS_DIR "/steam.png");
|
||||
}
|
||||
Reference in New Issue
Block a user