mirror of
https://github.com/logos-co/logos-package.git
synced 2026-08-27 18:21:17 +00:00
`icon` moves from a per-variant copy to a single assets/icon.png at the package root, so a host can read it without unpacking a platform build — the precondition for showing icons before install. Covered by the Merkle tree, so it is authenticated by manifest.sig for free. Adds `lgx add --icon` and Package::setIcon(). Validation (PNG, exactly 256x256) runs at verify/sign, not at create/add — a package is legitimately incomplete mid-assembly. Required for type ui_qml only; core modules render no tile. Manifests below 0.4.0 are exempt: icon: "" was legal at 0.3.0 and is what lgx create defaulted to, so enforcing it would make every published package uninstallable
677 lines
23 KiB
C++
677 lines
23 KiB
C++
#include <gtest/gtest.h>
|
|
#include "core/package.h"
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
|
|
#include "test_png.h"
|
|
#include <sstream>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
class CLITest : public ::testing::Test {
|
|
protected:
|
|
fs::path tempDir;
|
|
fs::path lgxBinary;
|
|
|
|
void SetUp() override {
|
|
tempDir = fs::temp_directory_path() / ("lgx_cli_test_" + std::to_string(rand()));
|
|
fs::create_directories(tempDir);
|
|
|
|
// Check for LGX_BINARY environment variable first
|
|
const char* envBinary = std::getenv("LGX_BINARY");
|
|
if (envBinary && fs::exists(envBinary)) {
|
|
lgxBinary = envBinary;
|
|
return;
|
|
}
|
|
|
|
// Find the lgx binary - try multiple locations
|
|
std::vector<fs::path> searchPaths = {
|
|
fs::current_path().parent_path() / "lgx", // Running from tests/ subdir
|
|
fs::current_path() / "lgx", // Running from build root
|
|
fs::current_path() / "build" / "lgx", // Running from project root
|
|
fs::current_path().parent_path() / "build" / "lgx" // Running from build/tests
|
|
};
|
|
|
|
for (const auto& path : searchPaths) {
|
|
if (fs::exists(path)) {
|
|
lgxBinary = path;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Binary not found, skip tests
|
|
GTEST_SKIP() << "lgx binary not found. Set LGX_BINARY env var or tried: "
|
|
<< searchPaths[0] << ", " << searchPaths[1] << ", "
|
|
<< searchPaths[2] << ", " << searchPaths[3];
|
|
}
|
|
|
|
void TearDown() override {
|
|
std::error_code ec;
|
|
fs::remove_all(tempDir, ec);
|
|
}
|
|
|
|
// Helper to run lgx command
|
|
int runLgx(const std::string& args, std::string* output = nullptr) {
|
|
std::string cmd = lgxBinary.string() + " " + args;
|
|
if (output) {
|
|
cmd += " 2>&1";
|
|
FILE* pipe = popen(cmd.c_str(), "r");
|
|
if (!pipe) return -1;
|
|
|
|
char buffer[128];
|
|
while (fgets(buffer, sizeof(buffer), pipe)) {
|
|
*output += buffer;
|
|
}
|
|
int status = pclose(pipe);
|
|
return WEXITSTATUS(status);
|
|
} else {
|
|
int status = system(cmd.c_str());
|
|
return WEXITSTATUS(status);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Test: lgx create <name>
|
|
// Verifies that the CLI can create a new skeleton package
|
|
TEST_F(CLITest, CreateCommand) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("create " + (tempDir / "test").string(), &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_TRUE(fs::exists(pkgPath));
|
|
EXPECT_NE(output.find("Created package"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx verify <valid-package>
|
|
// Verifies that the CLI correctly validates a well-formed package
|
|
// Commands: lgx create, lgx verify
|
|
TEST_F(CLITest, VerifyCommand_ValidPackage) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
runLgx("create " + (tempDir / "test").string());
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("verify " + pkgPath.string(), &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("valid"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx verify <invalid-package>
|
|
// Verifies that the CLI correctly rejects an invalid package file
|
|
// Commands: lgx verify
|
|
TEST_F(CLITest, VerifyCommand_InvalidPackage) {
|
|
fs::path invalidPkg = tempDir / "invalid.lgx";
|
|
std::ofstream(invalidPkg) << "not a valid package";
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("verify " + invalidPkg.string(), &output);
|
|
|
|
EXPECT_NE(exitCode, 0); // Should fail
|
|
EXPECT_FALSE(output.empty()); // Should have error message
|
|
}
|
|
|
|
// Test: lgx add <pkg> --variant <v> --files <single-file> -y
|
|
// Verifies adding a single file to a variant and that package remains valid
|
|
// Commands: lgx create, lgx add, lgx verify
|
|
TEST_F(CLITest, AddCommand_SingleFile) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testFile = tempDir / "lib.so";
|
|
|
|
// Create package and test file
|
|
runLgx("create " + (tempDir / "test").string());
|
|
std::ofstream(testFile) << "test content";
|
|
|
|
// Add variant
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
testFile.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Added"), std::string::npos);
|
|
|
|
// Verify still valid
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// Test: lgx add <pkg> --variant <v> --files <directory> --main <path> -y
|
|
// Verifies adding a directory to a variant with explicit main entry
|
|
// Commands: lgx create, lgx add, lgx verify
|
|
TEST_F(CLITest, AddCommand_Directory) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testDir = tempDir / "dist";
|
|
|
|
// Create package and test directory
|
|
runLgx("create " + (tempDir / "test").string());
|
|
fs::create_directories(testDir);
|
|
std::ofstream(testDir / "index.js") << "console.log('hello')";
|
|
std::ofstream(testDir / "lib.js") << "export {}";
|
|
|
|
// Add variant with directory
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v web -f " +
|
|
testDir.string() + " --main index.js -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Added"), std::string::npos);
|
|
|
|
// Verify still valid
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// Test: lgx add <pkg> --variant <existing-v> --files <new-file> -y
|
|
// Verifies variant replacement (no merge) - old content should be replaced
|
|
// Commands: lgx create, lgx add (twice), lgx verify
|
|
TEST_F(CLITest, AddCommand_ReplacesVariant) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path file1 = tempDir / "old.so";
|
|
fs::path file2 = tempDir / "new.so";
|
|
|
|
// Create package
|
|
runLgx("create " + (tempDir / "test").string());
|
|
|
|
// Add initial variant
|
|
std::ofstream(file1) << "old content";
|
|
runLgx("add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
file1.string() + " -y");
|
|
|
|
// Replace with new file
|
|
std::ofstream(file2) << "new content";
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
file2.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Replaced"), std::string::npos);
|
|
|
|
// Verify package is still valid
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// Test: lgx remove <pkg> --variant <v> -y
|
|
// Verifies removing a variant from a package
|
|
// Commands: lgx create, lgx add, lgx remove, lgx verify
|
|
TEST_F(CLITest, RemoveCommand) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testFile = tempDir / "lib.so";
|
|
|
|
// Setup: create package and add variant
|
|
runLgx("create " + (tempDir / "test").string());
|
|
std::ofstream(testFile) << "test content";
|
|
runLgx("add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
testFile.string() + " -y");
|
|
|
|
// Remove variant
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"remove " + pkgPath.string() + " -v linux-amd64 -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Removed"), std::string::npos);
|
|
|
|
// Verify package is still valid (empty but valid)
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// Test: lgx remove <pkg> --variant <nonexistent>
|
|
// Verifies error handling when removing non-existent variant
|
|
// Commands: lgx create, lgx remove
|
|
TEST_F(CLITest, RemoveCommand_NonExistent) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
|
|
// Create empty package
|
|
runLgx("create " + (tempDir / "test").string());
|
|
|
|
// Try to remove non-existent variant
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"remove " + pkgPath.string() + " -v nonexistent -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_NE(exitCode, 0); // Should fail
|
|
EXPECT_FALSE(output.empty()); // Should have error message
|
|
}
|
|
|
|
// Test: lgx --help
|
|
// Verifies that help text is displayed correctly
|
|
// Commands: lgx --help
|
|
TEST_F(CLITest, HelpCommand) {
|
|
std::string output;
|
|
int exitCode = runLgx("--help", &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Usage"), std::string::npos);
|
|
EXPECT_NE(output.find("create"), std::string::npos);
|
|
EXPECT_NE(output.find("add"), std::string::npos);
|
|
EXPECT_NE(output.find("remove"), std::string::npos);
|
|
EXPECT_NE(output.find("verify"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx --version
|
|
// Verifies that version information is displayed correctly
|
|
// Commands: lgx --version
|
|
TEST_F(CLITest, VersionCommand) {
|
|
std::string output;
|
|
int exitCode = runLgx("--version", &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("0.1.0"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx create <name> (when file already exists)
|
|
// Verifies error handling when trying to create over existing file
|
|
// Commands: lgx create (twice)
|
|
TEST_F(CLITest, CreateCommand_FileExists) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
|
|
// Create first time
|
|
runLgx("create " + (tempDir / "test").string());
|
|
|
|
// Try to create again
|
|
std::string output;
|
|
int exitCode = runLgx("create " + (tempDir / "test").string(), &output);
|
|
|
|
EXPECT_NE(exitCode, 0); // Should fail
|
|
EXPECT_NE(output.find("exists"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx add <pkg> --variant <v> --files <directory> (without --main)
|
|
// Verifies error handling when adding directory without required --main flag
|
|
// Commands: lgx create, lgx add
|
|
TEST_F(CLITest, AddCommand_DirectoryWithoutMain) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testDir = tempDir / "dist";
|
|
|
|
// Create package and test directory
|
|
runLgx("create " + (tempDir / "test").string());
|
|
fs::create_directories(testDir);
|
|
std::ofstream(testDir / "file.txt") << "content";
|
|
|
|
// Try to add directory without --main
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v web -f " + testDir.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_NE(exitCode, 0); // Should fail
|
|
EXPECT_NE(output.find("required"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(CLITest, AddCommand_UiQmlDirectoryWithoutMain) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testDir = tempDir / "dist";
|
|
|
|
runLgx("create " + (tempDir / "test").string());
|
|
fs::create_directories(testDir / "qml");
|
|
std::ofstream(testDir / "qml" / "Main.qml") << "import QtQuick 2.15\nItem {}";
|
|
|
|
auto pkgOpt = lgx::Package::load(pkgPath);
|
|
ASSERT_TRUE(pkgOpt.has_value());
|
|
pkgOpt->getManifest().type = "ui_qml";
|
|
pkgOpt->getManifest().view = "qml/Main.qml";
|
|
auto saveResult = pkgOpt->save(pkgPath);
|
|
ASSERT_TRUE(saveResult.success);
|
|
|
|
// 0.4.0 requires ui_qml packages to carry a conforming icon.
|
|
const std::string iconPath =
|
|
lgx_test::writePng((tempDir / "icon.png").string());
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v darwin-arm64 -f " + testDir.string() +
|
|
" --icon " + iconPath + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Added"), std::string::npos);
|
|
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
TEST_F(CLITest, AddCommand_UiQmlDirectoryWithViewFlag) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testDir = tempDir / "dist";
|
|
|
|
runLgx("create " + (tempDir / "test").string());
|
|
fs::create_directories(testDir / "qml");
|
|
std::ofstream(testDir / "qml" / "Main.qml") << "import QtQuick 2.15\nItem {}";
|
|
|
|
auto pkgOpt = lgx::Package::load(pkgPath);
|
|
ASSERT_TRUE(pkgOpt.has_value());
|
|
pkgOpt->getManifest().type = "ui_qml";
|
|
auto saveResult = pkgOpt->save(pkgPath);
|
|
ASSERT_TRUE(saveResult.success);
|
|
|
|
// 0.4.0 requires ui_qml packages to carry a conforming icon.
|
|
const std::string iconPath =
|
|
lgx_test::writePng((tempDir / "icon.png").string());
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v darwin-arm64 -f " + testDir.string()
|
|
+ " --view qml/Main.qml --icon " + iconPath + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Added"), std::string::npos);
|
|
|
|
exitCode = runLgx("verify " + pkgPath.string());
|
|
EXPECT_EQ(exitCode, 0);
|
|
|
|
auto reloaded = lgx::Package::load(pkgPath);
|
|
ASSERT_TRUE(reloaded.has_value());
|
|
EXPECT_EQ(reloaded->getManifest().view, "qml/Main.qml");
|
|
}
|
|
|
|
TEST_F(CLITest, AddCommand_UiQmlDirectoryWithoutView) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path testDir = tempDir / "dist";
|
|
|
|
runLgx("create " + (tempDir / "test").string());
|
|
fs::create_directories(testDir);
|
|
std::ofstream(testDir / "file.txt") << "content";
|
|
|
|
auto pkgOpt = lgx::Package::load(pkgPath);
|
|
ASSERT_TRUE(pkgOpt.has_value());
|
|
pkgOpt->getManifest().type = "ui_qml";
|
|
auto saveResult = pkgOpt->save(pkgPath);
|
|
ASSERT_TRUE(saveResult.success);
|
|
|
|
// 0.4.0 requires ui_qml packages to carry a conforming icon.
|
|
const std::string iconPath =
|
|
lgx_test::writePng((tempDir / "icon.png").string());
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"add " + pkgPath.string() + " -v darwin-arm64 -f " + testDir.string() +
|
|
" --icon " + iconPath + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_NE(exitCode, 0);
|
|
EXPECT_NE(output.find("view"), std::string::npos);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Merge Command Tests
|
|
// =============================================================================
|
|
|
|
// Helper to create a single-variant .lgx package with metadata
|
|
void createSingleVariantPackage(
|
|
const std::string& lgxBinary,
|
|
const fs::path& pkgPath,
|
|
const std::string& name,
|
|
const std::string& variant,
|
|
const std::string& fileContent
|
|
) {
|
|
fs::path dir = pkgPath.parent_path();
|
|
fs::path tmpFile = dir / (variant + "_file.so");
|
|
|
|
// Create the package using the logical name (so manifest.name matches across packages)
|
|
fs::path createdPath = dir / (name + ".lgx");
|
|
std::string cmd = lgxBinary + " create " + (dir / name).string() + " 2>&1";
|
|
system(cmd.c_str());
|
|
if (createdPath != pkgPath) {
|
|
fs::rename(createdPath, pkgPath);
|
|
}
|
|
|
|
// Create a test file and add as variant
|
|
std::ofstream(tmpFile) << fileContent;
|
|
cmd = lgxBinary + " add " + pkgPath.string() +
|
|
" -v " + variant + " -f " + tmpFile.string() + " -y 2>&1";
|
|
system(cmd.c_str());
|
|
}
|
|
|
|
// Test: lgx merge <pkg1.lgx> <pkg2.lgx>
|
|
// Verifies basic merge of two single-variant packages
|
|
TEST_F(CLITest, MergeCommand_BasicMerge) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
fs::path pkg2 = tempDir / "pkg2.lgx";
|
|
fs::path merged = tempDir / "merged.lgx";
|
|
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "test", "linux-amd64", "linux lib");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg2, "test", "darwin-arm64", "darwin lib");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"merge " + pkg1.string() + " " + pkg2.string() +
|
|
" -o " + merged.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("Merged"), std::string::npos);
|
|
EXPECT_TRUE(fs::exists(merged));
|
|
|
|
// Verify the merged package is valid
|
|
exitCode = runLgx("verify " + merged.string(), &output);
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// Test: lgx merge with duplicate variants (should fail)
|
|
TEST_F(CLITest, MergeCommand_DuplicateVariantsFails) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
fs::path pkg2 = tempDir / "pkg2.lgx";
|
|
fs::path merged = tempDir / "merged.lgx";
|
|
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "test", "linux-amd64", "lib v1");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg2, "test", "linux-amd64", "lib v2");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"merge " + pkg1.string() + " " + pkg2.string() +
|
|
" -o " + merged.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_NE(exitCode, 0);
|
|
EXPECT_NE(output.find("Duplicate variant"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx merge with --skip-duplicates
|
|
TEST_F(CLITest, MergeCommand_SkipDuplicates) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
fs::path pkg2 = tempDir / "pkg2.lgx";
|
|
fs::path merged = tempDir / "merged.lgx";
|
|
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "test", "linux-amd64", "lib v1");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg2, "test", "linux-amd64", "lib v2");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"merge " + pkg1.string() + " " + pkg2.string() +
|
|
" -o " + merged.string() + " --skip-duplicates -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("skipping"), std::string::npos);
|
|
EXPECT_TRUE(fs::exists(merged));
|
|
}
|
|
|
|
// Test: lgx merge with manifest mismatch (should fail)
|
|
TEST_F(CLITest, MergeCommand_ManifestMismatch) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
fs::path pkg2 = tempDir / "pkg2.lgx";
|
|
fs::path merged = tempDir / "merged.lgx";
|
|
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "pkg1", "linux-amd64", "lib1");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg2, "pkg2", "darwin-arm64", "lib2");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"merge " + pkg1.string() + " " + pkg2.string() +
|
|
" -o " + merged.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_NE(exitCode, 0);
|
|
EXPECT_NE(output.find("mismatch"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx merge with too few arguments
|
|
TEST_F(CLITest, MergeCommand_TooFewArgs) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "test", "linux-amd64", "lib");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("merge " + pkg1.string(), &output);
|
|
|
|
EXPECT_NE(exitCode, 0);
|
|
EXPECT_NE(output.find("At least two"), std::string::npos);
|
|
}
|
|
|
|
// Test: lgx merge three packages
|
|
TEST_F(CLITest, MergeCommand_ThreePackages) {
|
|
fs::path pkg1 = tempDir / "pkg1.lgx";
|
|
fs::path pkg2 = tempDir / "pkg2.lgx";
|
|
fs::path pkg3 = tempDir / "pkg3.lgx";
|
|
fs::path merged = tempDir / "merged.lgx";
|
|
|
|
createSingleVariantPackage(lgxBinary.string(), pkg1, "test", "linux-amd64", "linux amd64");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg2, "test", "linux-arm64", "linux arm64");
|
|
createSingleVariantPackage(lgxBinary.string(), pkg3, "test", "darwin-arm64", "darwin arm64");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"merge " + pkg1.string() + " " + pkg2.string() + " " + pkg3.string() +
|
|
" -o " + merged.string() + " -y",
|
|
&output
|
|
);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_TRUE(fs::exists(merged));
|
|
|
|
// Verify the merged package is valid
|
|
exitCode = runLgx("verify " + merged.string(), &output);
|
|
EXPECT_EQ(exitCode, 0);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Multi-variant package workflow
|
|
// =============================================================================
|
|
|
|
// Test: Multi-variant package workflow
|
|
// Verifies creating a package with multiple variants
|
|
// Commands: lgx create, lgx add (multiple), lgx verify
|
|
TEST_F(CLITest, MultiVariantWorkflow) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path linuxLib = tempDir / "lib_linux.so";
|
|
fs::path darwinLib = tempDir / "lib_darwin.dylib";
|
|
|
|
// Create package
|
|
runLgx("create " + (tempDir / "test").string());
|
|
|
|
// Add Linux variant
|
|
std::ofstream(linuxLib) << "linux library";
|
|
runLgx("add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
linuxLib.string() + " -y");
|
|
|
|
// Add Darwin variant
|
|
std::ofstream(darwinLib) << "darwin library";
|
|
runLgx("add " + pkgPath.string() + " -v darwin-arm64 -f " +
|
|
darwinLib.string() + " -y");
|
|
|
|
// Verify package with multiple variants
|
|
std::string output;
|
|
int exitCode = runLgx("verify " + pkgPath.string(), &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_NE(output.find("valid"), std::string::npos);
|
|
}
|
|
|
|
// ── lgx signature ────────────────────────────────────────────────────────
|
|
//
|
|
// Contract pinned by these tests:
|
|
// * unsigned package → stdout empty, exit 0
|
|
// * signed package → stdout = manifest.sig JSON, exit 0
|
|
// * missing path → stderr message, exit non-zero
|
|
// Tooling (the out-of-CI index builder over in logos-modules-release-base)
|
|
// relies on the exit-status-not-stream-length convention to distinguish
|
|
// "no signature" from "error" — regressing that contract would silently
|
|
// flip every unsigned package in a self-hosted catalog into a failure
|
|
// during index build, hence the explicit tests below.
|
|
|
|
// Test: lgx signature on an unsigned package
|
|
// Expected: empty stdout, exit 0 (the "I'm fine, just unsigned" path).
|
|
TEST_F(CLITest, SignatureCommand_UnsignedPackage) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path lib = tempDir / "lib.so";
|
|
|
|
runLgx("create " + (tempDir / "test").string());
|
|
std::ofstream(lib) << "payload";
|
|
runLgx("add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
lib.string() + " -y");
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("signature " + pkgPath.string(), &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_TRUE(output.empty())
|
|
<< "unsigned package must produce no stdout output; got: " << output;
|
|
}
|
|
|
|
// Test: lgx signature on a signed package
|
|
// Expected: stdout carries the raw manifest.sig JSON (must contain the
|
|
// signer's DID), exit 0. The content match is intentionally loose —
|
|
// looking for "did" / "signature" substrings is enough to confirm the
|
|
// blob came out, without coupling the test to the exact JSON layout.
|
|
TEST_F(CLITest, SignatureCommand_SignedPackage) {
|
|
fs::path pkgPath = tempDir / "test.lgx";
|
|
fs::path lib = tempDir / "lib.so";
|
|
fs::path keysDir = tempDir / "keys";
|
|
|
|
runLgx("create " + (tempDir / "test").string());
|
|
std::ofstream(lib) << "payload";
|
|
runLgx("add " + pkgPath.string() + " -v linux-amd64 -f " +
|
|
lib.string() + " -y");
|
|
runLgx("keygen --name testkey --output-dir " + keysDir.string());
|
|
int signExit = runLgx("sign " + pkgPath.string() +
|
|
" --key testkey --keys-dir " + keysDir.string());
|
|
ASSERT_EQ(signExit, 0) << "preflight: signing must succeed for this test";
|
|
|
|
std::string output;
|
|
int exitCode = runLgx("signature " + pkgPath.string(), &output);
|
|
|
|
EXPECT_EQ(exitCode, 0);
|
|
EXPECT_FALSE(output.empty()) << "signed package must produce stdout";
|
|
EXPECT_NE(output.find("\"did\""), std::string::npos)
|
|
<< "signature JSON should include a `did` field; got: " << output;
|
|
EXPECT_NE(output.find("\"signature\""), std::string::npos)
|
|
<< "signature JSON should include a `signature` field; got: " << output;
|
|
}
|
|
|
|
// Test: lgx signature with a missing path
|
|
// Expected: non-zero exit (the "real error, not unsigned" path).
|
|
TEST_F(CLITest, SignatureCommand_MissingPackage) {
|
|
std::string output;
|
|
int exitCode = runLgx(
|
|
"signature " + (tempDir / "does-not-exist.lgx").string(),
|
|
&output);
|
|
|
|
EXPECT_NE(exitCode, 0);
|
|
EXPECT_FALSE(output.empty()) << "missing package should print a message";
|
|
}
|