#include import cargoxx.linkdb; import cargoxx.util; import std; using cargoxx::linkdb::Database; using cargoxx::linkdb::Recipe; using cargoxx::util::ErrorCode; namespace { auto fresh_overlay() -> std::filesystem::path { auto d = std::filesystem::temp_directory_path() / std::format("cargoxx-overlay-test-{}", std::random_device{}()); std::filesystem::create_directories(d); return d / "overlay.sqlite"; } } // namespace TEST_CASE("open creates the overlay schema and file", "[linkdb][overlay]") { auto path = fresh_overlay(); auto db = Database::open(path); REQUIRE(db.has_value()); REQUIRE(std::filesystem::exists(path)); } TEST_CASE("add_manual then resolve returns the manual recipe", "[linkdb][overlay]") { auto db = Database::open(fresh_overlay()); REQUIRE(db.has_value()); Recipe r{ .nixpkgs_attr = "obscurelib", .find_package = "obscurelib CONFIG REQUIRED", .targets = {"obscurelib::obscurelib"}, .source = "manual", }; REQUIRE(db->add_manual("obscurelib", "*", r).has_value()); auto got = db->resolve("obscurelib", "1.0.0"); REQUIRE(got.has_value()); REQUIRE(got->nixpkgs_attr == "obscurelib"); REQUIRE(got->find_package == "obscurelib CONFIG REQUIRED"); REQUIRE(got->targets == std::vector{"obscurelib::obscurelib"}); REQUIRE(got->source == "manual"); } TEST_CASE("manual entry resolves on subsequent open", "[linkdb][overlay]") { auto db = Database::open(fresh_overlay()); REQUIRE(db.has_value()); Recipe r{ .nixpkgs_attr = "fmt_pinned", .find_package = "fmt CONFIG REQUIRED", .targets = {"fmt::fmt"}, .source = "manual", }; REQUIRE(db->add_manual("fmt", ">=10.0.0", r).has_value()); auto got = db->resolve("fmt", "10.2.0"); REQUIRE(got.has_value()); REQUIRE(got->nixpkgs_attr == "fmt_pinned"); REQUIRE(got->source == "manual"); } TEST_CASE("manual entry is constrained by version_range", "[linkdb][overlay]") { auto db = Database::open(fresh_overlay()); REQUIRE(db.has_value()); Recipe r{ .nixpkgs_attr = "fmt_v11_only", .find_package = "fmt CONFIG REQUIRED", .targets = {"fmt::fmt"}, .source = "manual", }; REQUIRE(db->add_manual("fmt", ">=11.0.0", r).has_value()); auto miss = db->resolve("fmt", "10.2.0"); REQUIRE_FALSE(miss.has_value()); REQUIRE(miss.error().code == ErrorCode::LinkdbUnknownPackage); auto manual = db->resolve("fmt", "11.0.0"); REQUIRE(manual.has_value()); REQUIRE(manual->source == "manual"); REQUIRE(manual->nixpkgs_attr == "fmt_v11_only"); } TEST_CASE("manual recipes persist across reopen", "[linkdb][overlay]") { auto path = fresh_overlay(); { auto db = Database::open(path); REQUIRE(db.has_value()); Recipe r{ .nixpkgs_attr = "persistlib", .find_package = "persistlib CONFIG REQUIRED", .targets = {"persistlib::persistlib"}, .source = "manual", }; REQUIRE(db->add_manual("persistlib", "*", r).has_value()); } auto db = Database::open(path); REQUIRE(db.has_value()); auto got = db->resolve("persistlib", "0.0.1"); REQUIRE(got.has_value()); REQUIRE(got->nixpkgs_attr == "persistlib"); REQUIRE(got->source == "manual"); } TEST_CASE("resolve with components on a manual recipe is rejected", "[linkdb][overlay]") { auto db = Database::open(fresh_overlay()); REQUIRE(db.has_value()); Recipe r{ .nixpkgs_attr = "weirdlib", .find_package = "weirdlib CONFIG REQUIRED", .targets = {"weirdlib::weirdlib"}, .source = "manual", }; REQUIRE(db->add_manual("weirdlib", "*", r).has_value()); auto got = db->resolve("weirdlib", "1.0.0", {"some_component"}); REQUIRE_FALSE(got.has_value()); REQUIRE(got.error().code == ErrorCode::LinkdbComponentNotSupported); } TEST_CASE("Database is move-constructible without leaking the handle", "[linkdb][overlay]") { auto db = Database::open(fresh_overlay()); REQUIRE(db.has_value()); Database moved = std::move(*db); Recipe r{ .nixpkgs_attr = "movelib", .find_package = "movelib CONFIG REQUIRED", .targets = {"movelib::movelib"}, .source = "manual", }; REQUIRE(moved.add_manual("movelib", "*", r).has_value()); auto got = moved.resolve("movelib", "1.0.0"); REQUIRE(got.has_value()); REQUIRE(got->source == "manual"); }