diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c8c152ed..500597d96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #1024: Added flag -export-python-deps to publish command
+- #962: Adding zpm "ci" command to install from a lock file
### Fixed
- #996: Ensure COS commands execute in exec under a dedicated, isolated context
diff --git a/src/cls/IPM/General/AbstractHistory.cls b/src/cls/IPM/General/AbstractHistory.cls
index 10dd29641..e54a4545b 100644
--- a/src/cls/IPM/General/AbstractHistory.cls
+++ b/src/cls/IPM/General/AbstractHistory.cls
@@ -8,8 +8,8 @@ Include (%IPM.Common, %IPM.Formatting)
Class %IPM.General.AbstractHistory Extends %Persistent [ Abstract, NoExtent ]
{
-/// Action of this history record. Can be load, install, uninstall or update
-Property Action As %String(VALUELIST = ",load,install,uninstall,update") [ Required ];
+/// Action of this history record. Can be load, install, ci, uninstall or update
+Property Action As %String(VALUELIST = ",load,install,ci,uninstall,update") [ Required ];
/// Name of the package being logged. This is not necessarily required, e.g. when loading a nonexistent directory.
Property Package As %IPM.DataType.ModuleName;
@@ -66,6 +66,11 @@ ClassMethod InstallInit(Package As %IPM.DataType.ModuleName) As %IPM.General.Abs
quit ..Init("install", Package)
}
+ClassMethod CleanInstallInit(Package As %IPM.DataType.ModuleName) As %IPM.General.AbstractHistory
+{
+ quit ..Init("ci", Package)
+}
+
ClassMethod LoadInit(Package As %IPM.DataType.ModuleName = "") As %IPM.General.AbstractHistory
{
// Package name may not known at this point, so use a placeholder
diff --git a/src/cls/IPM/General/LockFile.cls b/src/cls/IPM/General/LockFile.cls
index 4d0cac0cb..3a0f7230d 100644
--- a/src/cls/IPM/General/LockFile.cls
+++ b/src/cls/IPM/General/LockFile.cls
@@ -69,12 +69,12 @@ ClassMethod CreateLockFileForModule(
$$$ThrowOnError(lockFile.Dependencies.SetAt(dependencyVal, mod.Name))
// Add the dependency's repository to the lock file
- do AddRepositoryToLockFile(.lockFile, mod.Repository, verbose)
+ do ..AddRepositoryToLockFile(.lockFile, mod.Repository, verbose)
}
// Add repository for base module if not already added by a dependency
// Skip undefined repositories as that means the module was installed via the zpm "load" command
if (module.Repository '= "") {
- do AddRepositoryToLockFile(.lockFile, module.Repository, verbose)
+ do ..AddRepositoryToLockFile(.lockFile, module.Repository, verbose)
}
$$$ThrowOnError(lockFile.%JSONExportToStream(.lockFileJSON, "LockFileMapping"))
@@ -116,4 +116,88 @@ ClassMethod GetRepo(repoName As %String) As %IPM.Repo.Definition [ Internal ]
}
}
+ClassMethod InstallFromLockFile(
+ directory As %String,
+ ByRef params)
+{
+ set verbose = $get(params("Verbose"), 0)
+
+ set lockFilePath = ##class(%File).NormalizeFilename("module-lock.json", directory)
+ set lockFileJSON = ##class(%DynamicObject).%FromJSONFile(lockFilePath)
+
+ set repositories = lockFileJSON.%Get("repositories", {})
+ set dependencies = lockFileJSON.%Get("dependencies", {})
+
+ // Install repositories (if they don't already exist)
+ set repoIter = repositories.%GetIterator()
+ while repoIter.%GetNext(.repoName, .repoVals) {
+ if ##class(%IPM.Repo.Definition).ServerDefinitionKeyExists(repoName) {
+ if (verbose) {
+ write !, "Repo: "_repoName_" already exists, skipping creating new one from lock file"
+ }
+ continue
+ }
+ elseif (verbose) {
+ write !, "Creating repo: "_repoName_" from lock file"
+ }
+ do ##class(%IPM.Repo.Definition).CollectServerTypes(.types)
+ set repoClass = types(repoVals.type)
+ set repoVals.name = repoName
+ do $classmethod(repoClass, "LockFileValuesToModifiers", repoVals, .modifiers)
+ $$$ThrowOnError($classmethod(repoClass,"Configure",0,.modifiers,.tData,repoClass))
+ }
+
+ // Install modules (even if they already exist)
+ do ..GetOrderedDependenciesList(dependencies, .orderedDependenciesList)
+ set depName = ""
+ for i=1:1:$listlength(orderedDependenciesList) {
+ set depName = $list(orderedDependenciesList, i)
+ set depVals = dependencies.%Get(depName)
+
+ set version = depVals.version
+ set repository = depVals.repository
+
+ // Call CleanInstall() on dependency modules but set flag "LockFileInstallStarted" so we don't try installing from the dependency module's lock file
+ set commandInfo = "ci"
+ set commandInfo("data", "Verbose") = verbose
+ set commandInfo("parameters","module") = repository_"/"_depName
+ set commandInfo("parameters", "version") = version
+ set commandInfo("data", "LockFileInstallStarted") = 1
+ do ##class(%IPM.Main).CleanInstall(.commandInfo)
+ }
+}
+
+/// The dependencies list in a lock file is listed alphabetically.
+/// This method compiles the dependencies and creates an ordered list such that no module is listed
+/// before one of its dependencies. Can then trace the list this outputs and install in order
+ClassMethod GetOrderedDependenciesList(
+ dependencies As %DynamicObject,
+ ByRef orderedDependenciesList As %List = "") [ Internal ]
+{
+ set depIter = dependencies.%GetIterator()
+ while depIter.%GetNext(.depName, .depVals) {
+ do ..AddToOrderedDependenciesList(depName, dependencies, .orderedDependenciesList)
+ }
+}
+
+/// For a dependency, recursively adds all dependencies to the ordered list, followed by this dependency
+ClassMethod AddToOrderedDependenciesList(
+ dependencyName As %String,
+ dependencies As %DynamicObject,
+ ByRef orderedDependenciesList As %List) [ Internal, Private ]
+{
+ set depVals = dependencies.%Get(dependencyName)
+ set transientDeps = depVals.%Get("dependencies", {})
+ set transientIter = transientDeps.%GetIterator()
+ while transientIter.%GetNext(.transDepName) {
+ // If dependency hasn't been installed yet, then recursively run this method on it
+ if '$listfind(orderedDependenciesList, transDepName) {
+ do ..AddToOrderedDependenciesList(transDepName, dependencies, .orderedDependenciesList)
+ }
+ }
+ if '$listfind(orderedDependenciesList, dependencyName) {
+ set orderedDependenciesList = orderedDependenciesList _ $listbuild(dependencyName)
+ }
+}
+
}
diff --git a/src/cls/IPM/Lifecycle/Base.cls b/src/cls/IPM/Lifecycle/Base.cls
index 8fef53d56..05e3c8a98 100644
--- a/src/cls/IPM/Lifecycle/Base.cls
+++ b/src/cls/IPM/Lifecycle/Base.cls
@@ -1227,6 +1227,7 @@ Method %Export(
"changelog.md",
"license",
"requirements.txt",
+ "module-lock.json",
)
set tRes = ##class(%File).FileSetFunc(..Module.Root)
while tRes.%Next() {
diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls
index 5fce52dfa..3b32b55ce 100644
--- a/src/cls/IPM/Main.cls
+++ b/src/cls/IPM/Main.cls
@@ -791,6 +791,25 @@ generate /my/path -export 00000,PacketName2,IgnorePacket2^00000,PacketName3,Igno
history details 3 -phases
+
+Installs a module from a lock file
+
+ Installs a module from its lock file. Will first install all listed repositories followed by dependency modules and then the base module.
+
+
+
+
+ ci mymodule 3.0.0
+
+
+
+
+
+
+
+
+
+
}
@@ -1031,8 +1050,10 @@ ClassMethod ShellInternal(
do ..ModuleVersion(.tCommandInfo)
} elseif (tCommandInfo = "information") {
do ..Information(.tCommandInfo)
- } elseif (tCommandInfo = "history") {
- do ..History(.tCommandInfo)
+ } elseif (tCommandInfo = "history") {
+ do ..History(.tCommandInfo)
+ } elseif (tCommandInfo = "ci") {
+ do ..CleanInstall(.tCommandInfo)
}
} catch pException {
if (pException.Code = $$$ERCTRLC) {
@@ -2396,12 +2417,11 @@ ClassMethod Install(
$$$ThrowStatus($$$ERROR($$$GeneralError, "Deployed package '" _ tModuleName _ "' " _ tResult.VersionString _ " not supported on this platform " _ platformVersion _ "."))
}
}
- $$$ThrowOnError(log.SetSource(tResult.ServerName))
- $$$ThrowOnError(log.SetVersion(tResult.Version))
+ $$$ThrowOnError(log.SetSource(tResult.ServerName))
+ $$$ThrowOnError(log.SetVersion(tResult.Version))
$$$ThrowOnError(##class(%IPM.Utils.Module).LoadQualifiedReference(tResult, .tParams, , log))
}
} else {
- set tPrefix = ""
if (tModuleName '= "") {
if (tVersion '= "") {
$$$ThrowStatus($$$ERROR($$$GeneralError, tModuleName_" "_tVersion_" not found in any repository."))
@@ -2415,10 +2435,32 @@ ClassMethod Install(
}
}
} catch ex {
- $$$ThrowOnError(log.Finalize(ex.AsStatus(), devMode))
- throw ex
- }
- $$$ThrowOnError(log.Finalize($$$OK, devMode))
+ $$$ThrowOnError(log.Finalize(ex.AsStatus(), devMode))
+ throw ex
+ }
+ $$$ThrowOnError(log.Finalize($$$OK, devMode))
+}
+
+ClassMethod CleanInstall(ByRef commandInfo) [ Internal ]
+{
+ set moduleName = $get(commandInfo("parameters","module"))
+ set version = $get(commandInfo("parameters","version"))
+ set verbose = $get(commandInfo("data","Verbose"))
+ set log = ##class(%IPM.General.HistoryTemp).CleanInstallInit(moduleName)
+
+ // TODO: Add "path"? (see Update() for more info of calling install v load)
+
+ if verbose {
+ write !, "Going to run a clean install on "_moduleName
+ }
+
+ // Indicating to commandInfo that this is a clean install command, not an install or load command. commandInfo will be passed to either Install() or Load() to continue performing the update.
+ set commandInfo("data","cmd") = "ci"
+ set commandInfo("data","CleanInstall") = 1
+ set log = ##class(%IPM.General.HistoryTemp).UpdateInit(moduleName)
+
+ // Forward execution to install
+ do ..Install(.commandInfo, log)
}
ClassMethod Reinstall(ByRef pCommandInfo) [ Internal ]
diff --git a/src/cls/IPM/Repo/Definition.cls b/src/cls/IPM/Repo/Definition.cls
index b36b3b504..bdc2f9aa9 100644
--- a/src/cls/IPM/Repo/Definition.cls
+++ b/src/cls/IPM/Repo/Definition.cls
@@ -288,6 +288,16 @@ Method LockFileTypeGet()
return ..#MONIKER
}
+/// We use the "LockFileMapping" XData block to export a repo definition to a lock file.
+/// When importing from a lock file, doing an import and save using the same XData block won't work.
+/// Instead, we must create a set of modifiers and call %IPM.Repo.Definition::Configure()
+/// This method accepts the JSON repository values from a lock file and populates a modifers object to then call Configure() with
+ClassMethod LockFileValuesToModifiers(
+ lockFileValues As %DynamicObject,
+ Output modifiers) [ Abstract ]
+{
+}
+
Storage Default
{
diff --git a/src/cls/IPM/Repo/Filesystem/Definition.cls b/src/cls/IPM/Repo/Filesystem/Definition.cls
index 582b32405..788b0a9cf 100644
--- a/src/cls/IPM/Repo/Filesystem/Definition.cls
+++ b/src/cls/IPM/Repo/Filesystem/Definition.cls
@@ -357,11 +357,21 @@ ClassMethod ScanDirectory(
quit tSC
}
+ClassMethod LockFileValuesToModifiers(
+ lockFileValues As %DynamicObject,
+ Output modifiers)
+{
+ set modifiers("filesystem") = ""
+ set modifiers("name") = lockFileValues.%Get("name")
+ set modifiers("path") = lockFileValues.%Get("root")
+ set modifiers("depth") = lockFileValues.%Get("depth")
+ set modifiers("read-only") = lockFileValues.%Get("readOnly")
+}
+
XData LockFileMapping
{
-
diff --git a/src/cls/IPM/Repo/Oras/Definition.cls b/src/cls/IPM/Repo/Oras/Definition.cls
index 629e15831..0de8a7f9d 100644
--- a/src/cls/IPM/Repo/Oras/Definition.cls
+++ b/src/cls/IPM/Repo/Oras/Definition.cls
@@ -140,11 +140,34 @@ Method GetPublishingManager(ByRef status)
return ##class(%IPM.Repo.Oras.PublishManager).%Get(.status)
}
+ClassMethod LockFileValuesToModifiers(
+ lockFileValues As %DynamicObject,
+ Output modifiers)
+{
+ set modifiers("oras") = ""
+ set modifiers("name") = lockFileValues.%Get("name")
+ set modifiers("read-only") = lockFileValues.%Get("readOnly")
+ set modifiers("url") = lockFileValues.%Get("url")
+ set modifiers("namespace") = lockFileValues.%Get("orasNamespace")
+
+ // The following variables are set as system level variables for us to get
+ // Naming convention for those follow the name of the repository, first converting any '-' to '_',
+ // then removing everything except for alphabetic characters, numbers, and '_' to use as variable prefix
+ // lastly, adds a suffix based on the modifier
+ // Examples: -
+ // "registry" - "registry"
+ // "ORAS!Repo(5)?" - "ORASRepo5"
+ // "My-Repository-2" - "My_Repository_2"
+ set prefix = $zstrip($replace(modifiers("name"), "-", "_"), "*E'N'A")
+ set modifiers("username") = $system.Util.GetEnviron(prefix_"_username")
+ set modifiers("password") = $system.Util.GetEnviron(prefix_"_password")
+ set modifiers("token") = $system.Util.GetEnviron(prefix_"_token")
+}
+
XData LockFileMapping
{
-
diff --git a/src/cls/IPM/Repo/Remote/Definition.cls b/src/cls/IPM/Repo/Remote/Definition.cls
index d259b39a0..98eec5fd7 100644
--- a/src/cls/IPM/Repo/Remote/Definition.cls
+++ b/src/cls/IPM/Repo/Remote/Definition.cls
@@ -132,6 +132,29 @@ Method GetPublishingManager(ByRef status)
return ##class(%IPM.Repo.Remote.PublishManager).%Get(.status)
}
+ClassMethod LockFileValuesToModifiers(
+ lockFileValues As %DynamicObject,
+ Output modifiers)
+{
+ set modifiers("remote") = ""
+ set modifiers("name") = lockFileValues.%Get("name")
+ set modifiers("url") = lockFileValues.%Get("url")
+ set modifiers("read-only") = lockFileValues.%Get("readOnly")
+
+ // The following variables are set as system level variables for us to get
+ // Naming convention for those follow the name of the repository, first converting any '-' to '_',
+ // then removing everything except for alphabetic characters, numbers, and '_' to use as variable prefix
+ // lastly, adds a suffix based on the modifier
+ // Examples: -
+ // "registry" - "registry"
+ // "ORAS!Repo(5)?" - "ORASRepo5"
+ // "My-Repository-2" - "My_Repository_2"
+ set prefix = $zstrip($replace(modifiers("name"), "-", "_"), "*E'N'A")
+ set modifiers("username") = $system.Util.GetEnviron(prefix_"_username")
+ set modifiers("password") = $system.Util.GetEnviron(prefix_"_password")
+ set modifiers("token") = $system.Util.GetEnviron(prefix_"_token")
+}
+
Method LockFileTypeGet()
{
return ..#MONIKERALIAS
@@ -141,7 +164,6 @@ XData LockFileMapping
{
-
diff --git a/src/cls/IPM/Utils/Module.cls b/src/cls/IPM/Utils/Module.cls
index b99970acb..8c771a0c6 100644
--- a/src/cls/IPM/Utils/Module.cls
+++ b/src/cls/IPM/Utils/Module.cls
@@ -271,6 +271,9 @@ ClassMethod LoadModuleFromDirectory(
{
set tSC = $$$OK
try {
+ if $get(pParams("CleanInstall"), 0) && '$get(pParams("LockFileInstallStarted"), 0) {
+ do ##class(%IPM.General.LockFile).InstallFromLockFile(pDirectory, .pParams)
+ }
set tVerbose = $get(pParams("Verbose"))
// LoadNewModule goes all the way through Reload->Validate->Compile->Activate, also compiling the new module.
write:tVerbose !,"Loading from ",pDirectory,!
@@ -948,7 +951,6 @@ ClassMethod GetModuleNameFromXML(
/// 1
///
/// ```
-///
/// Returns results as multidimensional array
ClassMethod GetModuleDefaultsFromXML(
pDirectory As %String,
@@ -1205,7 +1207,12 @@ ClassMethod LoadNewModule(
if $get(params("CreateLockFile"), 0) && '$data(params("LockFileModule")){
set params("LockFileModule") = tModule.Name
}
- do ..LoadDependencies(tModule,, .params)
+
+ // If installing from a lock file, don't need to load dependencies since dependencies will be installed in order anyways
+ if ('$get(params("CleanInstall"), 0)) {
+ do ..LoadDependencies(tModule, .params)
+ }
+
set tSC = $system.OBJ.Load(pDirectory_"module.xml",$select(tVerbose:"d",1:"-d"),,.tLoadedList)
$$$ThrowOnError(tSC)
diff --git a/tests/integration_tests/Test/PM/Integration/LockFile.cls b/tests/integration_tests/Test/PM/Integration/LockFile.cls
index 15d96fa99..b1e53a1ac 100644
--- a/tests/integration_tests/Test/PM/Integration/LockFile.cls
+++ b/tests/integration_tests/Test/PM/Integration/LockFile.cls
@@ -25,7 +25,7 @@ Parameter ModuleE As String = "lock-mod-e-1-dep-0-transient";
/// Module with 1 dep w/ 1 transient & 1 dep w/o transient (E & B)
Parameter ModuleF As String = "lock-mod-f-2-deps-1-transient";
-/// Module with dependencies for all repository types (Module remote, Module oras, Module http, Module filesystem, & Module perforce)
+/// Module with dependencies for all repository types (Module remote, Module oras, Module http, Module filesystem)
Parameter ModuleG As String = "lock-mod-g-all-repo-types";
/// Module with multiple versions
@@ -36,10 +36,6 @@ Parameter ModuleH As String = "lock-mod-h-multiple-versions";
/// Base equivalent to Module F, just deleted the lock file
Parameter ModuleI As String = "lock-mod-i-no-prior-lock-file";
-/// Base equivalent to Module D, but different lock file
-/// Lock file as A->C->B instead of A->B->C (C is dependent on B)
-Parameter ModuleJ As String = "lock-mod-j-deps-misordered";
-
/// 2 dependencies: C & E, both with same transient dependency: A
Parameter ModuleK As String = "lock-mod-k-common-transient";
@@ -85,10 +81,9 @@ Method OnAfterOneTest() As %Status
Method Test01Module0Dependencies()
{
try {
- set moduleName = ..#ModuleA
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleA)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module0Dependencies.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test01Module0Dependencies.")
}
}
@@ -97,10 +92,9 @@ Method Test01Module0Dependencies()
Method Test02Module2Dependencies0Transient()
{
try {
- set moduleName = ..#ModuleC
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleC)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies0Transient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test02Module2Dependencies0Transient.")
}
}
@@ -109,10 +103,9 @@ Method Test02Module2Dependencies0Transient()
Method Test03Module1Dependency2Transient()
{
try {
- set moduleName = ..#ModuleD
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleD)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module1Dependency2Transient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test03Module1Dependency2Transient.")
}
}
@@ -121,17 +114,16 @@ Method Test03Module1Dependency2Transient()
Method Test04Module2Dependencies1Transient()
{
try {
- set moduleName = ..#ModuleF
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleF)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test04Module2Dependencies1Transient.")
}
}
/// Edge cases below:
-/// All repository types
+/// Test with all repository types: Filesystem, ORAS, & Remote
/// Uses Module G
-Method Test05ModuleDependenciesAllRepositoryTypes()
+Method Test05AllRepositoryTypes()
{
set sc = $$$OK
try {
@@ -149,7 +141,7 @@ Method Test05ModuleDependenciesAllRepositoryTypes()
set sc = ##class(%IPM.Main).Shell("publish "_remoteMod_" -r "_remoteRepo_" -v -export-deps 1")
do $$$AssertStatusOK(sc, "Successfully published module to remote repo")
// Uninstall mod and to then be installed from remote repo
- do ##class(%IPM.Main).Shell("uninstall "_remoteMod)
+ do ##class(%IPM.Main).Shell("uninstall -r "_remoteMod)
// Publish a module to an ORAS repository
set orasRepo = "lock-file-oras"
@@ -167,19 +159,23 @@ Method Test05ModuleDependenciesAllRepositoryTypes()
set sc = ##class(%IPM.Main).Shell("repo -delete -name lock-file-other-repos")
do $$$AssertStatusOK(sc,"Removed lock-file-other-repos repo successfully.")
- // Now that modules and repositories are set up, do the actual test
- set moduleName = "lock-mod-oras"
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ // Now that modules have been published to their respective repositories, run the test
+ do ..AssertInstallCreatesLockFileAsExpected(..#ModuleG)
+
+ // Remove all repositories except lock-file-edge (where Module G comes from) to confirm all types get created correctly when installing from lock file
+ do ##class(%IPM.Main).Shell("repo -delete -name lock-file-base")
+ do ##class(%IPM.Main).Shell("repo -delete -name "_remoteRepo)
+ do ##class(%IPM.Main).Shell("repo -delete -name "_orasRepo)
+
+ // Install from Module G's lock file
+ do ..AssertInstallFromLockFileAsExpected(..#ModuleG)
- // Go back to the initial state by uninstalling the module, dependencies, and repositories used for this test
- do ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
- do $$$AssertStatusOK(sc, "Uninstalled "_moduleName_" and dependencies at the end of the test")
do ##class(%IPM.Main).Shell("repo -delete -name "_remoteRepo)
do $$$AssertStatusOK(sc, "Removed "_remoteRepo_" at the end of the test")
do ##class(%IPM.Main).Shell("repo -delete -name "_orasRepo)
do $$$AssertStatusOK(sc, "Removed "_orasRepo_" at the end of the test")
- } catch (ex) {
- set sc = ex.AsStatus()
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test05ModuleDependenciesAllRepositoryTypes.")
}
}
@@ -204,8 +200,20 @@ Method Test06ModuleMultipleVersions()
set areLockFilesEqual = ..AreLockFilesEqual(lockFilePath, ..#ExpectedFilesDir_latestLockFileName)
do $$$AssertTrue(areLockFilesEqual, "Lock file contents for "_moduleName_" v"_latestVersion_" match expected values")
- // Go back to the initial state by uninstalling the module and dependencies at the end of the test
- do ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ // Uninstall module to prepare for install from lock file
+ set sc = ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ do $$$AssertStatusOK(sc, "Uninstalled "_moduleName)
+
+ // Install module from lock file
+ set sc = ##class(%IPM.Main).Shell("ci "_moduleName)
+ do $$$AssertStatusOK(sc, "Able to install "_moduleName_" v"_latestVersion_" from the lock file")
+
+ // Confirm classes and dependencies get installed as well
+ do $classmethod("LockModH.Class1", "MethodB")
+
+ // Uninstall module before testing with other version
+ set sc = ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ do $$$AssertStatusOK(sc, "Uninstalled "_moduleName)
@@ -224,22 +232,32 @@ Method Test06ModuleMultipleVersions()
do $$$AssertTrue(areLockFilesEqual, "Lock file contents for "_moduleName_" v"_olderVersion_" match expected values")
// Go back to the initial state by uninstalling the module and dependencies at the end of the test
- do ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ set sc = ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ do $$$AssertStatusOK(sc, "Uninstalled "_moduleName)
+
+ // Install module from lock file
+ set sc = ##class(%IPM.Main).Shell("ci "_moduleName_" "_olderVersion)
+ do $$$AssertStatusOK(sc, "Able to install "_moduleName_" v"_olderVersion_" from the lock file")
+
+ // Confirm classes and dependencies get installed as well
+ do $classmethod("LockModH.Class1", "MethodA")
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test06ModuleMultipleVersions.")
}
}
/// Module with no pre-existing lock file
-/// - [TODO, with "ci" command] zpm "ci" should fail due to not being able to locate the lock file
/// - Test creation of a lock file, for a module that did not already have one.
/// - Differs from other tests which overwrite pre-existing lock files
/// - Both cases SHOULD be functionally equivalent
+/// - zpm "ci" should fail due to not being able to locate the lock file
/// Uses Module I
Method Test07ModuleNoLockFile()
{
try {
set moduleName = ..#ModuleI
+
+ // Install and create lock file for module
do ..AssertInstallCreatesLockFileAsExpected(moduleName)
// Go back to the initial state by deleting lock file for this module at the end of the test
@@ -248,43 +266,63 @@ Method Test07ModuleNoLockFile()
if '##class(%File).Delete(lockFilePath) {
$$$ThrowStatus($$$ERROR($$$GeneralError,$$$FormatText("Failed to delete lock file located at: %1", lockFilePath)))
}
+
+ // Uninstall module to prepare to install from lock file
+ set sc = ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ do $$$AssertStatusOK(sc, "Uninstalled "_moduleName)
+
+ // This should fail due to no lock file
+ set sc = ##class(%IPM.Main).Shell("ci "_moduleName)
+ do $$$AssertStatusNotOK(sc, "zpm ""ci"" fails on module "_moduleName_"due to not having a lock file")
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test07ModuleNoLockFile.")
}
}
-/// Try install on a lock file that lists dependencies out of order
-/// Uses Module J
-Method Test08ModuleDependenciesOutOfOrder()
-{
- // TODO: Implement with zpm "ci" addition
-}
-
/// 2 dependencies for a module have the same transient to add to the lock file
/// Uses Module K
-Method Test09Module2DependenciesSameTransient()
+Method Test08Module2DependenciesSameTransient()
{
try {
- set moduleName = ..#ModuleK
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleK)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2DependenciesSameTransient.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test09Module2DependenciesSameTransient.")
}
}
/// Tests writing and installing from a lock file for a module with a more complex nested dependencies setup
/// Uses Module L
-Method Test10ComplexNestedDependencies()
+Method Test09ComplexNestedDependencies()
{
try {
- set moduleName = ..#ModuleL
- do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ do ..ExecuteGenericLockFileTest(..#ModuleL)
} catch e {
- do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in TestComplexNestedDependencies.")
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Test10ComplexNestedDependencies.")
}
}
-/// Base cases of creating and installing from a lock file
+/// Generic lock file test are steps that are shared across many cases above. The steps are:
+/// 1. Install and create lock file for module
+/// 2. Check that the lock file generated matches expected values
+/// 3. Uninstall the module (and dependencies)
+/// 4. Install from lock file via zpm "ci"
+/// 5. Call method in installed module (which in turn calls dependency methods) to confirm "ci" loads all modules correctly
+Method ExecuteGenericLockFileTest(
+ moduleName As %String,
+ className As %String = "")
+{
+ // Install and create lock file for module
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+
+ // Uninstall module to prepare to install from lock file
+ set sc = ##class(%IPM.Main).Shell("uninstall -r "_moduleName)
+ do $$$AssertStatusOK(sc, "Uninstalled "_moduleName)
+
+ // Install module from lock file
+ do ..AssertInstallFromLockFileAsExpected(moduleName, className)
+}
+
+/// Base cases of creating a lock file upon install of a module
Method AssertInstallCreatesLockFileAsExpected(moduleName As %String)
{
// Do an initial install of the module and create a lock file for it
@@ -310,13 +348,35 @@ ClassMethod AreLockFilesEqual(
expectedLockFilePath As %String) As %Boolean
{
try {
- set actualLockFileContents = ##class(%DynamicAbstractObject).%FromJSONFile(actualLockFilePath)
- set expectedLockFileContents = ##class(%DynamicAbstractObject).%FromJSONFile(expectedLockFilePath)
- return actualLockFileContents.%ToJSON() = expectedLockFileContents.%ToJSON()
+ set actualLockFileContents = ##class(%DynamicAbstractObject).%FromJSONFile(actualLockFilePath)
+ set expectedLockFileContents = ##class(%DynamicAbstractObject).%FromJSONFile(expectedLockFilePath)
+ return actualLockFileContents.%ToJSON() = expectedLockFileContents.%ToJSON()
} catch (ex) {
// Should error if we fail to open either the actual or expected file
return ex.AsStatus()
}
}
+/// Base case of installing a module from a lock file and confirming everything gets loaded as expected
+Method AssertInstallFromLockFileAsExpected(
+ moduleName As %String,
+ className As %String = "")
+{
+ set sc = ##class(%IPM.Main).Shell("ci "_moduleName)
+ do $$$AssertStatusOK(sc, "Installed "_moduleName_" via the lock file successfully")
+
+ set sc = $$$OK
+ try {
+ if (className = "") {
+ // Letter of the module is in the 10th place: "lock-mod-x-..."
+ set packageLetter = $zconvert($extract(moduleName, 10), "u")
+ set className = "LockMod"_packageLetter_".Class1"
+ }
+ do $classmethod(className, "MethodA")
+ } catch (ex) {
+ set sc = ex.AsStatus()
+ }
+ do $$$AssertStatusOK(sc, "Called MethodA() to confirm "_moduleName_" and dependency classes loaded correctly")
+}
+
}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-g-all-repo-types.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-g-all-repo-types.json
index f9c1353bf..7d0d60846 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-g-all-repo-types.json
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-g-all-repo-types.json
@@ -1,14 +1,70 @@
{
"name": "lock-mod-g-all-repo-types",
- "version": "1.0.0",
+ "version": "3.0.0",
"repository": "lock-file-edge",
"lockFileVersion": "1",
- "repositories": {},
+ "repositories": {
+ "lock-file-base": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/",
+ "depth": 0
+ },
+ "lock-file-edge": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/",
+ "depth": 0
+ },
+ "lock-file-oras": {
+ "type": "oras",
+ "readOnly": false,
+ "url": "http://oras:5000"
+ },
+ "lock-file-remote": {
+ "type": "remote",
+ "readOnly": false,
+ "url": "http://registry:52773/registry"
+ }
+ },
"dependencies": {
- "lock-mod-remote": {},
- "lock-mod-oras": {},
- "lock-mod-h-multiple-versionsttp": {},
- "lock-mod-f-2-deps-1-transientilesystem": {},
- "lock-mod-perforce": {}
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-b-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-c-2-deps-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0",
+ "lock-mod-b-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-oras": {
+ "version": "1.0.0",
+ "repository": "lock-file-oras",
+ "dependencies": {
+ "lock-mod-c-2-deps-0-transient": "^1.0.0",
+ "lock-mod-remote": "^1.0.0"
+ }
+ },
+ "lock-mod-remote": {
+ "version": "1.0.0",
+ "repository": "lock-file-remote",
+ "dependencies": {
+ "lock-mod-e-1-dep-0-transient": "^1.0.0"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-oras.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-oras.json
index 4d6c5fc86..222396971 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-oras.json
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-oras.json
@@ -38,9 +38,19 @@
"lock-mod-b-no-deps": "^1.0.0"
}
},
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ },
"lock-mod-remote": {
"version": "1.0.0",
- "repository": "lock-file-remote"
+ "repository": "lock-file-remote",
+ "dependencies": {
+ "lock-mod-e-1-dep-0-transient": "^1.0.0"
+ }
}
}
}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-remote.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-remote.json
new file mode 100644
index 000000000..bf13209a2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-remote.json
@@ -0,0 +1,32 @@
+{
+ "name": "lock-mod-remote",
+ "version": "1.0.0",
+ "repository": "lock-file-remote",
+ "lockFileVersion": "1",
+ "repositories": {
+ "lock-file-base": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/",
+ "depth": 0
+ },
+ "lock-file-remote": {
+ "type": "remote",
+ "readOnly": false,
+ "url": "http://registry:52773/registry"
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/cls/LockModG/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/cls/LockModG/Class1.cls
index c39de71bc..65dc265d8 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/cls/LockModG/Class1.cls
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/cls/LockModG/Class1.cls
@@ -5,10 +5,10 @@ ClassMethod MethodA()
{
write !, "This is ##class(LockModG.Class1).MethodA()"
- write !, "Now calling dependency classes (LockModRemote, LockModORAS, LockModC)"
+ write !, "Now calling dependency classes (LockModB, LockModRemote, LockModORAS)"
+ do ##class(LockModB.Class1).MethodA()
do ##class(LockModRemote.Class1).MethodA()
do ##class(LockModORAS.Class1).MethodA()
- do ##class(LockModC.Class1).MethodA()
}
}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module-lock.json
new file mode 100644
index 000000000..7d0d60846
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module-lock.json
@@ -0,0 +1,70 @@
+{
+ "name": "lock-mod-g-all-repo-types",
+ "version": "3.0.0",
+ "repository": "lock-file-edge",
+ "lockFileVersion": "1",
+ "repositories": {
+ "lock-file-base": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/",
+ "depth": 0
+ },
+ "lock-file-edge": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/",
+ "depth": 0
+ },
+ "lock-file-oras": {
+ "type": "oras",
+ "readOnly": false,
+ "url": "http://oras:5000"
+ },
+ "lock-file-remote": {
+ "type": "remote",
+ "readOnly": false,
+ "url": "http://registry:52773/registry"
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-b-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-c-2-deps-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0",
+ "lock-mod-b-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-oras": {
+ "version": "1.0.0",
+ "repository": "lock-file-oras",
+ "dependencies": {
+ "lock-mod-c-2-deps-0-transient": "^1.0.0",
+ "lock-mod-remote": "^1.0.0"
+ }
+ },
+ "lock-mod-remote": {
+ "version": "1.0.0",
+ "repository": "lock-file-remote",
+ "dependencies": {
+ "lock-mod-e-1-dep-0-transient": "^1.0.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml
index 31c9fc088..40d3f1e23 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml
@@ -7,15 +7,15 @@
- lock-mod-remote
+ lock-mod-b
^1.0.0
- lock-mod-oras
+ lock-mod-remote
^1.0.0
- lock-mod-c-2-deps-0-transient
+ lock-mod-oras
^1.0.0
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/cls/LockModH/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/cls/LockModH/Class1.cls
index 213d1512c..7597fba68 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/cls/LockModH/Class1.cls
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/cls/LockModH/Class1.cls
@@ -5,8 +5,7 @@ ClassMethod MethodA()
{
write !, "This is LockModH v2 ##class(LockModH.Class1).MethodA()"
- write !, "Now calling dependency classes (LockModB, LockModE)"
- do ##class(LockModB.Class1).MethodA()
+ write !, "Now calling dependency classes (LockModA, LockModE)"
do ##class(LockModE.Class1).MethodA()
}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/cls/LockModH/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/cls/LockModH/Class1.cls
index f0e91ba5e..cae94974c 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/cls/LockModH/Class1.cls
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/cls/LockModH/Class1.cls
@@ -1,14 +1,14 @@
Class LockModH.Class1
{
-ClassMethod MethodA()
+/// Method renamed from MethodA in v2 to MethodB in v3
+ClassMethod MethodB()
{
- write !, "This is LockModH v3 ##class(LockModH.Class1).MethodA()"
+ write !, "This is LockModH v3 ##class(LockModH.Class1).MethodB()"
- write !, "Now calling dependency classes (LockModA, LockModB, LockModE)"
- do ##class(LockModA.Class1).MethodA()
- do ##class(LockModB.Class1).MethodA()
+ write !, "Now calling dependency classes (LockModE, LockModB)"
do ##class(LockModE.Class1).MethodA()
+ do ##class(LockModB.Class1).MethodA()
}
}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/cls/LockModJ/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/cls/LockModJ/Class1.cls
deleted file mode 100644
index 52b1281bb..000000000
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/cls/LockModJ/Class1.cls
+++ /dev/null
@@ -1,12 +0,0 @@
-Class LockModJ.Class1
-{
-
-ClassMethod MethodA()
-{
- write !, "This is ##class(LockModJ.Class1).MethodA()"
-
- write !, "Now calling dependency classes (LockModC)"
- do ##class(LockModC.Class1).MethodA()
-}
-
-}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/module.xml
deleted file mode 100644
index 4139d4590..000000000
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/module.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- lock-mod-j-deps-misordered
- 1.0.0
-
-
-
- lock-mod-c-2-deps-0-transient
- ^1.0.0
-
-
-
-
-
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module-lock.json
new file mode 100644
index 000000000..222396971
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module-lock.json
@@ -0,0 +1,56 @@
+{
+ "name": "lock-mod-oras",
+ "version": "1.0.0",
+ "repository": "lock-file-oras",
+ "lockFileVersion": "1",
+ "repositories": {
+ "lock-file-base": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/",
+ "depth": 0
+ },
+ "lock-file-oras": {
+ "type": "oras",
+ "readOnly": false,
+ "url": "http://oras:5000"
+ },
+ "lock-file-remote": {
+ "type": "remote",
+ "readOnly": false,
+ "url": "http://registry:52773/registry"
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-b-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-c-2-deps-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0",
+ "lock-mod-b-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ },
+ "lock-mod-remote": {
+ "version": "1.0.0",
+ "repository": "lock-file-remote",
+ "dependencies": {
+ "lock-mod-e-1-dep-0-transient": "^1.0.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module-lock.json
new file mode 100644
index 000000000..bf13209a2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module-lock.json
@@ -0,0 +1,32 @@
+{
+ "name": "lock-mod-remote",
+ "version": "1.0.0",
+ "repository": "lock-file-remote",
+ "lockFileVersion": "1",
+ "repositories": {
+ "lock-file-base": {
+ "type": "filesystem",
+ "readOnly": false,
+ "root": "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/",
+ "depth": 0
+ },
+ "lock-file-remote": {
+ "type": "remote",
+ "readOnly": false,
+ "url": "http://registry:52773/registry"
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ },
+ "lock-mod-e-1-dep-0-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-a-no-deps": "^1.0.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module.xml
index e3f271bf0..7a244a371 100644
--- a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module.xml
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module.xml
@@ -5,6 +5,12 @@
lock-mod-remote
1.0.0
+
+
+ lock-mod-e-1-dep-0-transient
+ ^1.0.0
+
+
\ No newline at end of file