// Standalone tests for reasampler::package::import_plan — no REAPER, no filesystem, // no test framework. Every one of the four collision classes (sample id, bank-folder // file name, content hash, bank display name) is exercised here, which is the point of // the module: the whole collision rule set is decidable from strings and hashes. #include "../src/core/package/import_plan.h" #include #include #include #include "../src/core/tracking/tracking_authority.h" using namespace reasampler; using namespace reasampler::package; using reasampler::model::Sample; using reasampler::model::SlotMap; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) static const char* kProjectDir = "/proj"; static const char* kTag = "1754000000"; // --- fixtures ---------------------------------------------------------------- static PackageEntry entry(const std::string& fileName, const std::string& id, const std::string& hash) { PackageEntry e; e.fileName = fileName; e.byteLength = 64; e.byteHash = "0011223344556677"; e.sample.id = id; e.sample.displayName = id; e.sample.relativePath = "reasampler_bank/" + fileName; e.sample.contentHash = hash; return e; } static PackageManifest manifestOf(std::vector entries, const std::string& bankName) { PackageManifest m; m.bankDisplayName = bankName; m.entries = std::move(entries); return m; } // A book carrying the named banks, in order, each with a caller-supplied id. static BankBook bookWithBanks(const std::vector& names) { BankBook book; for (std::size_t i = 0; i < names.size(); ++i) book.createBank("bank-" + std::to_string(i), names[i]); return book; } static const PlannedEntry& landed(const ImportPlan& plan, std::size_t manifestIndex) { return plan.entries[manifestIndex]; } // --- the bank-name probe (collision class 4) --------------------------------- static std::string plannedName(const std::vector& existingBanks, const std::string& packageBankName) { const BankBook book = bookWithBanks(existingBanks); return planImport(manifestOf({}, packageBankName), book, kProjectDir, {}, kTag) .bankDisplayName; } static void testFreeSeedIsKeptVerbatim() { CHECK(plannedName({"Percussion"}, "Drums") == "Drums"); const ImportPlan plan = planImport(manifestOf({}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(!plan.bankNameAdjusted); CHECK(plan.seedBankName == "Drums"); } static void testFoldedCollisionTakesTheFirstSuffix() { // The book's fold is case- and whitespace-insensitive, so "drums" blocks "Drums". CHECK(plannedName({"drums"}, "Drums") == "Drums 2"); CHECK(plannedName({" DRUMS "}, "Drums") == "Drums 2"); const ImportPlan plan = planImport(manifestOf({}, "Drums"), bookWithBanks({"drums"}), kProjectDir, {}, kTag); CHECK(plan.bankNameAdjusted); CHECK(plan.seedBankName == "Drums"); // the message needs what was asked for } static void testProbeFillsAGap() { // First-free-ascending, not highest-plus-one: "Drums 2" is free, so it wins. CHECK(plannedName({"Drums", "Drums 3"}, "Drums") == "Drums 2"); } static void testSeedIsNeverReparsed() { // "Drums 2" colliding lands as "Drums 2 2", NOT "Drums 3" — a bare trailing integer // cannot be told from a name the user wrote. CHECK(plannedName({"Drums 2"}, "Drums 2") == "Drums 2 2"); CHECK(plannedName({"Kit 808"}, "Kit 808") == "Kit 808 2"); } static void testBlankRecordedNameFallsBackToTheDefault() { CHECK(plannedName({}, "") == kDefaultImportBankName); CHECK(plannedName({}, " \t ") == kDefaultImportBankName); // And the fallback is a seed like any other, so a second one suffixes. CHECK(plannedName({kDefaultImportBankName}, "") == std::string(kDefaultImportBankName) + " 2"); } static void testPoolExportLandsAsANamedBank() { // The destination's pool always exists and always carries the protected name // "Pool", so a pool export imports as a NAMED bank "Pool 2" — intended, not a glitch. CHECK(plannedName({}, "Pool") == "Pool 2"); const ImportPlan plan = planImport(manifestOf({}, "Pool"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.bankNameAdjusted); } static void testRepeatedImportsWalkTheSuffixUpwards() { CHECK(plannedName({"B"}, "B") == "B 2"); CHECK(plannedName({"B", "B 2"}, "B") == "B 3"); } // --- sample ids (collision class 1) ------------------------------------------ static void testEveryIdIsRemintedUnderTheImportPrefix() { const PackageManifest m = manifestOf({entry("kick.wav", "cap-1-kick.wav", "h1"), entry("snare.wav", "cap-2-snare.wav", "h2")}, "Drums"); const ImportPlan plan = planImport(m, bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.landCount == 2); for (const PlannedEntry& e : plan.entries) { CHECK(e.sample.id.rfind(kImportIdPrefix, 0) == 0); CHECK(e.sample.id != "cap-1-kick.wav"); CHECK(e.sample.id != "cap-2-snare.wav"); } CHECK(plan.entries[0].sample.id != plan.entries[1].sample.id); } static void testReimportingIntoTheSourceProjectRemintsRatherThanCollides() { // The package came FROM this project, so its ids are the ones already in use. BankBook book = bookWithBanks({"B"}); Sample existing; existing.id = "cap-1-kick.wav"; existing.relativePath = "reasampler_bank/kick.wav"; existing.contentHash = "h1"; book.index("bank-0")->add(existing); const PackageManifest m = manifestOf({entry("kick.wav", "cap-1-kick.wav", "h1")}, "B"); const ImportPlan plan = planImport(m, book, kProjectDir, {"kick.wav"}, kTag); CHECK(plan.bankDisplayName == "B 2"); CHECK(landed(plan, 0).sample.id != "cap-1-kick.wav"); // The hash lives in another bank; cross-bank dedup is deliberately not enforced, // so the entry still lands rather than collapsing onto B's copy. CHECK(landed(plan, 0).action == EntryAction::Land); CHECK(landed(plan, 0).renamed); } static void testParentIsRemappedWhenItTravelledInThePackage() { PackageEntry parent = entry("kick.wav", "cap-parent", "h1"); PackageEntry child = entry("kick_r2.wav", "cap-child", "h2"); child.sample.provenance = model::Provenance{"cap-parent", "fx-snapshot"}; const ImportPlan plan = planImport(manifestOf({parent, child}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(landed(plan, 1).sample.provenance.has_value()); CHECK(landed(plan, 1).sample.provenance->parentSampleId == landed(plan, 0).sample.id); CHECK(landed(plan, 1).sample.provenance->fxChainSnapshot == "fx-snapshot"); } static void testParentIsRemappedEvenWhenItFollowsTheChild() { // Manifest order does not constrain lineage, so the remap runs after every id is minted. PackageEntry child = entry("kick_r2.wav", "cap-child", "h2"); child.sample.provenance = model::Provenance{"cap-parent", ""}; PackageEntry parent = entry("kick.wav", "cap-parent", "h1"); const ImportPlan plan = planImport(manifestOf({child, parent}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(landed(plan, 0).sample.provenance->parentSampleId == landed(plan, 1).sample.id); } static void testForeignParentIsClearedNotCarried() { PackageEntry child = entry("kick.wav", "cap-child", "h1"); child.sample.provenance = model::Provenance{"cap-not-in-this-package", "fx"}; const ImportPlan plan = planImport(manifestOf({child}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(landed(plan, 0).sample.provenance.has_value()); CHECK(landed(plan, 0).sample.provenance->parentSampleId.empty()); CHECK(landed(plan, 0).sample.provenance->fxChainSnapshot == "fx"); } // --- bank-folder file names (collision class 2) ------------------------------ static void testAFreeBankLegalNameIsKept() { const ImportPlan plan = planImport(manifestOf({entry("kick.wav", "a", "h1")}, "Drums"), bookWithBanks({}), kProjectDir, {"unrelated.wav"}, kTag); CHECK(landed(plan, 0).destFileName == "kick.wav"); CHECK(!landed(plan, 0).renamed); CHECK(plan.collisionRenameCount == 0); CHECK(plan.sanitizeRenameCount == 0); CHECK(landed(plan, 0).sample.relativePath == "reasampler_bank/kick.wav"); } static void testATakenNameIsMintedFreshAndNeverOverwritten() { const ImportPlan plan = planImport(manifestOf({entry("kick.wav", "a", "h1")}, "Drums"), bookWithBanks({}), kProjectDir, {"kick.wav"}, kTag); CHECK(landed(plan, 0).destFileName != "kick.wav"); CHECK(landed(plan, 0).renamed); // A genuine folder-name collision, not a spelling mint. CHECK(plan.collisionRenameCount == 1); CHECK(plan.sanitizeRenameCount == 0); CHECK(landed(plan, 0).sample.relativePath == "reasampler_bank/" + landed(plan, 0).destFileName); } static void testTheFolderNameCheckFoldsAsciiCase() { // Windows and the default APFS would land "kick.wav" onto "KICK.WAV". const ImportPlan plan = planImport(manifestOf({entry("kick.wav", "a", "h1")}, "Drums"), bookWithBanks({}), kProjectDir, {"KICK.WAV"}, kTag); CHECK(landed(plan, 0).destFileName != "kick.wav"); CHECK(landed(plan, 0).renamed); // The case-fold hit is still a collision, not a spelling mint. CHECK(plan.collisionRenameCount == 1); CHECK(plan.sanitizeRenameCount == 0); } static void testTwoEntriesNeverLandOnOneName() { // Two package names that differ only by case are one destination file. const ImportPlan plan = planImport(manifestOf({entry("kick.wav", "a", "h1"), entry("Kick.wav", "b", "h2")}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.landCount == 2); CHECK(landed(plan, 0).destFileName != landed(plan, 1).destFileName); } static void testAnUnsanitaryNameIsMintedRatherThanLandedVerbatim() { const ImportPlan plan = planImport(manifestOf({entry("Hit One.wav", "a", "h1")}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(landed(plan, 0).destFileName.find(' ') == std::string::npos); CHECK(landed(plan, 0).renamed); // No collision here (the bank folder is empty) — this is a sanitize mint. CHECK(plan.sanitizeRenameCount == 1); CHECK(plan.collisionRenameCount == 0); } // --- content hash (collision class 3) ---------------------------------------- static void testAnAlreadyLandedHashCollapsesWithoutAWrite() { const ImportPlan plan = planImport(manifestOf({entry("kick.wav", "a", "same"), entry("kick_copy.wav", "b", "same")}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.landCount == 1); CHECK(plan.collapseCount == 1); CHECK(landed(plan, 0).action == EntryAction::Land); CHECK(landed(plan, 1).action == EntryAction::Collapse); // No name is claimed for it — a dedup that wrote a file would manufacture an orphan. CHECK(landed(plan, 1).destFileName.empty()); // Every manifest entry still yields exactly one planned entry: planImport is total. CHECK(plan.entries.size() == 2); } static void testAParentPointingAtACollapsedEntryResolvesToTheSurvivor() { PackageEntry first = entry("kick.wav", "cap-first", "same"); PackageEntry dupe = entry("kick_copy.wav", "cap-dupe", "same"); PackageEntry child = entry("kick_r2.wav", "cap-child", "other"); child.sample.provenance = model::Provenance{"cap-dupe", ""}; const ImportPlan plan = planImport(manifestOf({first, dupe, child}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(landed(plan, 2).sample.provenance->parentSampleId == landed(plan, 0).sample.id); } static void testAnEmptyHashNeverCollapses() { // Mirrors findByHash: an unhashable entry does not participate in dedup. const ImportPlan plan = planImport(manifestOf({entry("a.wav", "a", ""), entry("b.wav", "b", "")}, "Drums"), bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.landCount == 2); CHECK(plan.collapseCount == 0); } // --- slots ------------------------------------------------------------------- static void testSlotsRideAlongOverTheRemintedIds() { PackageManifest m = manifestOf({entry("kick.wav", "cap-a", "h1"), entry("snare.wav", "cap-b", "h2")}, "Drums"); // A gap the package carried: slot 0 empty, occupants at 1 and 3. m.slots = SlotMap::fromEntries({{"cap-a", 1}, {"cap-b", 3}}); const ImportPlan plan = planImport(m, bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.slots.slotOf(landed(plan, 0).sample.id) == 1); CHECK(plan.slots.slotOf(landed(plan, 1).sample.id) == 3); // The package's own ids are gone from the map — a foreign id never enters the index. CHECK(plan.slots.slotOf("cap-a") == -1); } static void testACollapsedEntryDoesNotDoubleOccupyASlot() { PackageManifest m = manifestOf({entry("kick.wav", "cap-a", "same"), entry("kick_copy.wav", "cap-b", "same")}, "Drums"); m.slots = SlotMap::fromEntries({{"cap-a", 0}, {"cap-b", 1}}); const ImportPlan plan = planImport(m, bookWithBanks({}), kProjectDir, {}, kTag); CHECK(plan.slots.size() == 1); CHECK(plan.slots.slotOf(landed(plan, 0).sample.id) == 0); } // --- the ledger gate --------------------------------------------------------- static void testDegradedLedgerStatusesRefuseAndTheUsableOnesDoNot() { CHECK(importLedgerRefusal(tracking::LedgerStatus::Unreadable) == LedgerRefusal::Malformed); CHECK(importLedgerRefusal(tracking::LedgerStatus::FutureVersion) == LedgerRefusal::FutureVersion); CHECK(importLedgerRefusal(tracking::LedgerStatus::Fresh) == LedgerRefusal::None); CHECK(importLedgerRefusal(tracking::LedgerStatus::Loaded) == LedgerRefusal::None); } static void testAnUndecodableUsageKeyBlocksPruneButNotImport() { // The tempting reuse of PruneReport::blockedByTracking would silently refuse an // import over a key that only ever governs what a DELETION may touch. tracking::OriginLedger ledger; wire::UsageFoldResult usage; usage.abortPrune = true; usage.offendingKeys = {"rsusage_{ABC}"}; const tracking::TrackingState state{tracking::LedgerStatus::Loaded, ledger, usage}; CHECK(tracking::pruneProtection(state).blocked); CHECK(importLedgerRefusal(tracking::LedgerStatus::Loaded) == LedgerRefusal::None); } // Pins the delegation itself: importLedgerRefusal must refuse EXACTLY the statuses // ledgerDegraded() calls degraded, over every value the enum has today. A gate that // re-derived its own notion of "degraded" could silently diverge from this the moment // either side changes without the other. static void testImportLedgerRefusalDelegatesToLedgerDegraded() { const tracking::LedgerStatus all[] = { tracking::LedgerStatus::Fresh, tracking::LedgerStatus::Loaded, tracking::LedgerStatus::Unreadable, tracking::LedgerStatus::FutureVersion, }; for (tracking::LedgerStatus s : all) CHECK((importLedgerRefusal(s) != LedgerRefusal::None) == tracking::ledgerDegraded(s)); } // --- the ledger-refusal message (pure, so both channels are assertable without a DAW) -- static void testLedgerRefusalMessageIsEmptyForNone() { CHECK(ledgerRefusalMessage(LedgerRefusal::None, "reasampler").empty()); } static void testLedgerRefusalMessageNamesTheChannelCorrectNamespace() { // The two real namespaces (app_version.h): stable "reasampler", beta "reasampler_beta". const std::string stable = ledgerRefusalMessage(LedgerRefusal::Malformed, "reasampler"); CHECK(stable.find("\"reasampler\"") != std::string::npos); CHECK(stable.find("reasampler_beta") == std::string::npos); const std::string beta = ledgerRefusalMessage(LedgerRefusal::Malformed, "reasampler_beta"); CHECK(beta.find("\"reasampler_beta\"") != std::string::npos); } static void testLedgerRefusalMessageDistinguishesMalformedFromFutureVersion() { const std::string malformed = ledgerRefusalMessage(LedgerRefusal::Malformed, "reasampler"); const std::string futureVersion = ledgerRefusalMessage(LedgerRefusal::FutureVersion, "reasampler"); CHECK(malformed != futureVersion); CHECK(malformed.find("malformed") != std::string::npos); CHECK(futureVersion.find("NEWER version") != std::string::npos); } int main() { testFreeSeedIsKeptVerbatim(); testFoldedCollisionTakesTheFirstSuffix(); testProbeFillsAGap(); testSeedIsNeverReparsed(); testBlankRecordedNameFallsBackToTheDefault(); testPoolExportLandsAsANamedBank(); testRepeatedImportsWalkTheSuffixUpwards(); testEveryIdIsRemintedUnderTheImportPrefix(); testReimportingIntoTheSourceProjectRemintsRatherThanCollides(); testParentIsRemappedWhenItTravelledInThePackage(); testParentIsRemappedEvenWhenItFollowsTheChild(); testForeignParentIsClearedNotCarried(); testAFreeBankLegalNameIsKept(); testATakenNameIsMintedFreshAndNeverOverwritten(); testTheFolderNameCheckFoldsAsciiCase(); testTwoEntriesNeverLandOnOneName(); testAnUnsanitaryNameIsMintedRatherThanLandedVerbatim(); testAnAlreadyLandedHashCollapsesWithoutAWrite(); testAParentPointingAtACollapsedEntryResolvesToTheSurvivor(); testAnEmptyHashNeverCollapses(); testSlotsRideAlongOverTheRemintedIds(); testACollapsedEntryDoesNotDoubleOccupyASlot(); testDegradedLedgerStatusesRefuseAndTheUsableOnesDoNot(); testAnUndecodableUsageKeyBlocksPruneButNotImport(); testImportLedgerRefusalDelegatesToLedgerDegraded(); testLedgerRefusalMessageIsEmptyForNone(); testLedgerRefusalMessageNamesTheChannelCorrectNamespace(); testLedgerRefusalMessageDistinguishesMalformedFromFutureVersion(); if (g_fail == 0) std::printf("import_plan: all tests passed\n"); return g_fail == 0 ? 0 : 1; }