Files
logos-package/tests/test_manifest.cpp

658 lines
18 KiB
C++

#include <gtest/gtest.h>
#include "core/manifest.h"
using namespace lgx;
// Valid manifest JSON for reuse
static const char* VALID_MANIFEST_JSON = R"({
"manifestVersion": "0.1.0",
"name": "testpkg",
"version": "1.0.0",
"description": "Test package",
"author": "Test Author",
"type": "library",
"category": "test",
"icon": "icon.png",
"dependencies": ["dep1", "dep2"],
"main": {
"linux-amd64": "lib/test.so",
"darwin-arm64": "lib/test.dylib"
}
})";
// =============================================================================
// Parsing Tests
// =============================================================================
TEST(ManifestTest, FromJson_ValidManifest) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
EXPECT_EQ(manifest->manifestVersion, "0.1.0");
EXPECT_EQ(manifest->name, "testpkg");
EXPECT_EQ(manifest->version, "1.0.0");
EXPECT_EQ(manifest->description, "Test package");
EXPECT_EQ(manifest->author, "Test Author");
EXPECT_EQ(manifest->type, "library");
EXPECT_EQ(manifest->category, "test");
EXPECT_EQ(manifest->icon, "icon.png");
EXPECT_EQ(manifest->dependencies.size(), 2);
EXPECT_EQ(manifest->main.size(), 2);
}
TEST(ManifestTest, FromJson_MissingManifestVersion) {
const char* json = R"({
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
}
TEST(ManifestTest, FromJson_MissingName) {
const char* json = R"({
"manifestVersion": "0.1.0",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
}
TEST(ManifestTest, FromJson_MissingMain) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": []
})";
auto manifest = Manifest::fromJson(json);
ASSERT_TRUE(manifest.has_value());
EXPECT_TRUE(manifest->main.empty());
}
TEST(ManifestTest, FromJson_ViewOnlyUiQmlManifest) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "ui_qml",
"category": "",
"icon": "",
"dependencies": [],
"view": "qml/Main.qml"
})";
auto manifest = Manifest::fromJson(json);
ASSERT_TRUE(manifest.has_value());
EXPECT_TRUE(manifest->main.empty());
EXPECT_EQ(manifest->view, "qml/Main.qml");
}
TEST(ManifestTest, FromJson_InvalidViewType) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "ui_qml",
"category": "",
"icon": "",
"dependencies": [],
"view": {}
})";
auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
EXPECT_FALSE(Manifest::getLastError().empty());
}
TEST(ManifestTest, FromJson_InvalidJson) {
const char* json = "{ not valid json }";
auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
EXPECT_FALSE(Manifest::getLastError().empty());
}
TEST(ManifestTest, FromJson_MissingIcon) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"dependencies": [],
"main": {}
})";
auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
}
TEST(ManifestTest, FromJson_EmptyDependencies) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
auto manifest = Manifest::fromJson(json);
ASSERT_TRUE(manifest.has_value());
EXPECT_TRUE(manifest->dependencies.empty());
}
// =============================================================================
// Serialization Tests
// =============================================================================
TEST(ManifestTest, ToJson_Roundtrip) {
auto original = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(original.has_value());
std::string json = original->toJson();
auto parsed = Manifest::fromJson(json);
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->manifestVersion, original->manifestVersion);
EXPECT_EQ(parsed->name, original->name);
EXPECT_EQ(parsed->version, original->version);
EXPECT_EQ(parsed->icon, original->icon);
EXPECT_EQ(parsed->main, original->main);
}
TEST(ManifestTest, ToJson_RoundtripPreservesViewWithoutMain) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "ui_qml",
"category": "",
"icon": "",
"dependencies": [],
"view": "qml/Main.qml"
})";
auto original = Manifest::fromJson(json);
ASSERT_TRUE(original.has_value());
std::string serialized = original->toJson();
auto parsed = Manifest::fromJson(serialized);
ASSERT_TRUE(parsed.has_value());
EXPECT_TRUE(parsed->main.empty());
EXPECT_EQ(parsed->view, "qml/Main.qml");
}
TEST(ManifestTest, ToJson_RoundtripPreservesDisplayName) {
const char* json = R"({
"manifestVersion": "0.3.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "core",
"category": "",
"icon": "",
"dependencies": [],
"display_name": "Friendly Label"
})";
auto original = Manifest::fromJson(json);
ASSERT_TRUE(original.has_value());
EXPECT_EQ(original->displayName, "Friendly Label");
std::string serialized = original->toJson();
auto parsed = Manifest::fromJson(serialized);
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->displayName, "Friendly Label");
}
TEST(ManifestTest, ToJson_OmitsDisplayNameWhenUnset) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
ASSERT_TRUE(manifest->displayName.empty());
// Should not emit "display_name" in the serialized form so older
// packages round-trip byte-identically.
std::string json = manifest->toJson();
EXPECT_EQ(json.find("display_name"), std::string::npos);
}
TEST(ManifestTest, ToJson_Deterministic) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
std::string json1 = manifest->toJson();
std::string json2 = manifest->toJson();
EXPECT_EQ(json1, json2);
}
// =============================================================================
// Validation Tests
// =============================================================================
TEST(ManifestTest, Validate_ValidManifest) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
auto result = manifest->validate();
EXPECT_TRUE(result.valid);
EXPECT_TRUE(result.errors.empty());
}
TEST(ManifestTest, Validate_UnsupportedVersion) {
Manifest m;
m.manifestVersion = "2.0.0"; // Major version 2 not supported
m.name = "test";
m.version = "1.0.0";
auto result = m.validate();
EXPECT_FALSE(result.valid);
EXPECT_FALSE(result.errors.empty());
}
TEST(ManifestTest, Validate_EmptyName) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "";
m.version = "1.0.0";
auto result = m.validate();
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, Validate_EmptyVersion) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "";
auto result = m.validate();
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, Validate_InvalidMainPath) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.main["linux"] = "/absolute/path"; // Invalid - absolute path
auto result = m.validate();
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, Validate_InvalidViewPath) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.type = "ui_qml";
m.view = "../qml/Main.qml";
auto result = m.validate();
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, Validate_UiQmlMissingViewIsInvalid) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.type = "ui_qml";
// view intentionally left empty
auto result = m.validate();
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("view"), std::string::npos);
}
// =============================================================================
// Completeness Constraint Tests
// =============================================================================
TEST(ManifestTest, ValidateCompleteness_Valid) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
std::set<std::string> variants = {"linux-amd64", "darwin-arm64"};
auto result = manifest->validateCompleteness(variants);
EXPECT_TRUE(result.valid);
}
TEST(ManifestTest, ValidateCompleteness_MissingVariantDir) {
auto manifest = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(manifest.has_value());
// Only one variant directory exists, but main has two
std::set<std::string> variants = {"linux-amd64"};
auto result = manifest->validateCompleteness(variants);
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, ValidateCompleteness_MissingMainEntry) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.main["linux-amd64"] = "lib.so";
// Two variant directories exist, but main only has one
std::set<std::string> variants = {"linux-amd64", "darwin-arm64"};
auto result = m.validateCompleteness(variants);
EXPECT_FALSE(result.valid);
}
TEST(ManifestTest, ValidateCompleteness_ViewOnlyUiQmlWithoutMainIsValid) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.type = "ui_qml";
m.view = "qml/Main.qml";
std::set<std::string> variants = {"linux-amd64", "darwin-arm64"};
auto result = m.validateCompleteness(variants);
EXPECT_TRUE(result.valid);
}
TEST(ManifestTest, ValidateCompleteness_CaseInsensitive) {
Manifest m;
m.manifestVersion = "0.1.0";
m.name = "test";
m.version = "1.0.0";
m.main["linux-amd64"] = "lib.so";
// Variant directory with different case
std::set<std::string> variants = {"Linux-AMD64"};
auto result = m.validateCompleteness(variants);
EXPECT_TRUE(result.valid);
}
// =============================================================================
// Variant Key Normalization Tests
// =============================================================================
TEST(ManifestTest, SetMain_NormalizesKey) {
Manifest m;
m.setMain("Linux-AMD64", "lib.so");
EXPECT_TRUE(m.main.find("linux-amd64") != m.main.end());
EXPECT_TRUE(m.main.find("Linux-AMD64") == m.main.end());
}
TEST(ManifestTest, GetMain_CaseInsensitive) {
Manifest m;
m.setMain("linux-amd64", "lib.so");
auto result = m.getMain("Linux-AMD64");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, "lib.so");
}
TEST(ManifestTest, RemoveMain) {
Manifest m;
m.setMain("linux-amd64", "lib.so");
m.setMain("darwin-arm64", "lib.dylib");
EXPECT_EQ(m.main.size(), 2);
m.removeMain("linux-amd64");
EXPECT_EQ(m.main.size(), 1);
EXPECT_FALSE(m.getMain("linux-amd64").has_value());
EXPECT_TRUE(m.getMain("darwin-arm64").has_value());
}
TEST(ManifestTest, GetVariants) {
Manifest m;
m.setMain("linux-amd64", "lib.so");
m.setMain("darwin-arm64", "lib.dylib");
m.setMain("web", "index.js");
auto variants = m.getVariants();
EXPECT_EQ(variants.size(), 3);
EXPECT_TRUE(variants.count("linux-amd64") > 0);
EXPECT_TRUE(variants.count("darwin-arm64") > 0);
EXPECT_TRUE(variants.count("web") > 0);
}
// =============================================================================
// Name Normalization Tests
// =============================================================================
TEST(ManifestTest, NormalizeName) {
Manifest m;
m.name = "MyPackage";
m.normalizeName();
EXPECT_EQ(m.name, "mypackage");
}
TEST(ManifestTest, NormalizeVariantKeys) {
Manifest m;
m.main["Linux-AMD64"] = "lib.so";
m.main["Darwin-ARM64"] = "lib.dylib";
m.normalizeVariantKeys();
EXPECT_EQ(m.main.size(), 2);
EXPECT_TRUE(m.main.find("linux-amd64") != m.main.end());
EXPECT_TRUE(m.main.find("darwin-arm64") != m.main.end());
}
// =============================================================================
// Version Support Tests
// =============================================================================
TEST(ManifestTest, IsVersionSupported_Major0) {
EXPECT_TRUE(Manifest::isVersionSupported("0.1.0"));
EXPECT_TRUE(Manifest::isVersionSupported("0.2.0"));
EXPECT_TRUE(Manifest::isVersionSupported("0.99.99"));
}
TEST(ManifestTest, IsVersionSupported_Major1Plus) {
EXPECT_FALSE(Manifest::isVersionSupported("1.0.0"));
EXPECT_FALSE(Manifest::isVersionSupported("2.0.0"));
}
TEST(ManifestTest, IsVersionSupported_Invalid) {
EXPECT_FALSE(Manifest::isVersionSupported("invalid"));
EXPECT_FALSE(Manifest::isVersionSupported(""));
}
// =============================================================================
// Default Constructor Test
// =============================================================================
TEST(ManifestTest, DefaultConstructor) {
Manifest m;
EXPECT_EQ(m.manifestVersion, Manifest::CURRENT_VERSION);
EXPECT_TRUE(m.name.empty());
EXPECT_TRUE(m.version.empty());
EXPECT_TRUE(m.main.empty());
}
// =============================================================================
// CompareMetadata Tests
// =============================================================================
TEST(ManifestTest, CompareMetadata_IdenticalManifests) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
auto result = a->compareMetadata(*b);
EXPECT_TRUE(result.valid);
EXPECT_TRUE(result.errors.empty());
}
TEST(ManifestTest, CompareMetadata_DifferentMainOnly) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
// Change the main field — should still match
b->main.clear();
b->setMain("web", "index.js");
auto result = a->compareMetadata(*b);
EXPECT_TRUE(result.valid);
}
TEST(ManifestTest, CompareMetadata_DifferentName) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
b->name = "otherpkg";
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("name"), std::string::npos);
}
TEST(ManifestTest, CompareMetadata_DifferentVersion) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
b->version = "2.0.0";
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("version"), std::string::npos);
}
TEST(ManifestTest, CompareMetadata_DifferentDisplayName) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
b->displayName = "Other Label";
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("display_name"), std::string::npos);
}
TEST(ManifestTest, CompareMetadata_DifferentDependencies) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
b->dependencies.push_back("extra-dep");
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("dependencies"), std::string::npos);
}
TEST(ManifestTest, CompareMetadata_DifferentView) {
const char* viewJsonA = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "ui_qml",
"category": "",
"icon": "",
"dependencies": [],
"view": "qml/Main.qml"
})";
const char* viewJsonB = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "ui_qml",
"category": "",
"icon": "",
"dependencies": [],
"view": "qml/Other.qml"
})";
auto a = Manifest::fromJson(viewJsonA);
auto b = Manifest::fromJson(viewJsonB);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
ASSERT_FALSE(result.errors.empty());
EXPECT_NE(result.errors[0].find("view"), std::string::npos);
}
TEST(ManifestTest, CompareMetadata_MultipleDifferences) {
auto a = Manifest::fromJson(VALID_MANIFEST_JSON);
auto b = Manifest::fromJson(VALID_MANIFEST_JSON);
ASSERT_TRUE(a.has_value());
ASSERT_TRUE(b.has_value());
b->name = "otherpkg";
b->version = "2.0.0";
b->author = "Other Author";
auto result = a->compareMetadata(*b);
EXPECT_FALSE(result.valid);
EXPECT_GE(result.errors.size(), 3);
}