diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e6015a3..01a198db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #950: Added support for listing installed Python packages using `list -python`, `list -py` and `list-installed -python`
- #822: The CPF resource processor now supports system expressions and macros in CPF merge files
- #578 Added functionality to record and display IPM history of install, uninstall, load, and update
+- #961: Adding creation of a lock file for a module by using the `-create-lockfile` flag on install.
### Changed
- #316: All parameters, except developer mode, included with a `load`, `install` or `update` command will be propagated to dependencies
diff --git a/src/cls/IPM/DataType/VersionString.cls b/src/cls/IPM/DataType/VersionString.cls
new file mode 100644
index 00000000..ed8b7dad
--- /dev/null
+++ b/src/cls/IPM/DataType/VersionString.cls
@@ -0,0 +1,7 @@
+Class %IPM.DataType.VersionString Extends %Library.String [ ClassType = datatype ]
+{
+
+/// The maximum number of characters the string can contain.
+Parameter MAXLEN As INTEGER = 100;
+
+}
diff --git a/src/cls/IPM/General/LockFile.cls b/src/cls/IPM/General/LockFile.cls
new file mode 100644
index 00000000..4d0cac0c
--- /dev/null
+++ b/src/cls/IPM/General/LockFile.cls
@@ -0,0 +1,119 @@
+/// This class is used for interacting with a lock file for a given module
+/// Covers functionality for both creating a lock file and installing from a lock file
+Class %IPM.General.LockFile Extends (%RegisteredObject, %JSON.Adaptor)
+{
+
+/// Name of the JSON lock file
+Parameter LockFileName As String = "module-lock.json";
+
+/// Name of the module the lock file is for
+Property ModuleName As %IPM.DataType.ModuleName(%JSONFIELDNAME = "name") [ Required ];
+
+/// Version string of the module
+Property VersionString As %IPM.DataType.VersionString(%JSONFIELDNAME = "version") [ Required ];
+
+/// Repository where the base module used to create the lock file comes from
+Property Repository As %IPM.DataType.RepoName(%JSONFIELDNAME = "repository");
+
+/// Version of the lock file schema used in creating the lock file
+Property LockFileVersion As %String(%JSONFIELDNAME = "lockFileVersion", MAXLEN = "") [ InitialExpression = "1" ];
+
+/// Array of repository definitions from where at least one of the base module or dependency comes from
+Property Repositories As array Of %IPM.Repo.Definition(%JSONFIELDNAME = "repositories");
+
+/// Array of dependency modules required by the module. Ordered by least dependent to most dependent
+Property Dependencies As array Of %IPM.General.LockFile.Dependency(%JSONFIELDNAME = "dependencies");
+
+/// When given the name of a module, creates the lock file for it and saves it to module.Root_..#LockFileName
+/// flatDependencyList is the output from ##class(%IPM.Utils.Module).GetFlatDependencyListFromInvertedDependencyGraph()
+ClassMethod CreateLockFileForModule(
+ module As %IPM.Storage.Module,
+ ByRef flatDependencyList,
+ ByRef params)
+{
+ set verbose = $get(params("Verbose"), 0)
+ if (verbose) {
+ write !, "Creating lock file for module "_module.DisplayName
+ }
+
+ // Create lock file object and set values for the base module
+ set lockFile = ##class(%IPM.General.LockFile).%New()
+ set lockFile.ModuleName = module.Name
+ set lockFile.VersionString = module.VersionString
+ set lockFile.Repository = module.Repository
+
+ // Iterate over module dependency list
+ // For each module:
+ // 1. Set values for module name, version, repository (name), and direct dependencies
+ // 2. Add repository information to the list (if the module is from a new repo)
+ set moduleName = ""
+ for i = 1:1:flatDependencyList.Count() {
+ set moduleName = flatDependencyList.GetAt(i).Name
+ set mod = ##class(%IPM.Storage.Module).NameOpen(moduleName, 0, .sc)
+ $$$ThrowOnError(sc)
+
+ if (verbose) {
+ write !, "Adding "_mod.DisplayName_" to the lock file"
+ }
+
+ // Add the dependency to the lock file
+ set dependencyVal = ##class(%IPM.General.LockFile.Dependency).%New()
+ set dependencyVal.VersionString = mod.VersionString
+ set dependencyVal.Repository = mod.Repository
+ set depKey = ""
+ for {
+ set depMod = mod.Dependencies.GetNext(.depKey)
+ quit:depKey=""
+ $$$ThrowOnError(dependencyVal.Dependencies.SetAt(depMod.VersionString, depMod.Name))
+ }
+ $$$ThrowOnError(lockFile.Dependencies.SetAt(dependencyVal, mod.Name))
+
+ // Add the dependency's repository to the lock file
+ 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)
+ }
+
+ $$$ThrowOnError(lockFile.%JSONExportToStream(.lockFileJSON, "LockFileMapping"))
+
+ set lockFilePath = module.Root_..#LockFileName
+ if (verbose) {
+ write !, "Saving lock file for "_module.Name_" to: "_lockFilePath
+ }
+ set file = ##class(%Stream.FileCharacter).%New()
+ $$$ThrowOnError(file.LinkToFile(lockFilePath))
+ $$$ThrowOnError(file.CopyFrom(lockFileJSON))
+ $$$ThrowOnError(file.%Save())
+}
+
+/// Adds a module's repository to the lock file
+ClassMethod AddRepositoryToLockFile(
+ ByRef lockFile As %IPM.General.LockFile,
+ repositoryName As %String,
+ verbose As %Boolean = 0) [ Internal ]
+{
+ set repo = ..GetRepo(repositoryName)
+ if 'lockFile.Repositories.IsDefined(repo.Name) {
+ if (verbose) {
+ write !, "Adding new repository to the lock file: "_repo.Name
+ }
+ $$$ThrowOnError(lockFile.Repositories.SetAt(repo, repo.Name))
+ }
+}
+
+/// Returns a repo based on its name
+ClassMethod GetRepo(repoName As %String) As %IPM.Repo.Definition [ Internal ]
+{
+ if ##class(%IPM.Repo.Definition).ServerDefinitionKeyExists(repoName, .id) {
+ set repo = ##class(%IPM.Repo.Definition).%OpenId(id, , .sc)
+ $$$ThrowOnError(sc)
+ return repo
+ } else {
+ $$$ThrowStatus($$$ERROR($$$GeneralError,$$$FormatText("Tried getting repo ""%1"" but none found with that name", repoName)))
+ }
+}
+
+}
diff --git a/src/cls/IPM/General/LockFile/Dependency.cls b/src/cls/IPM/General/LockFile/Dependency.cls
new file mode 100644
index 00000000..d6b0a237
--- /dev/null
+++ b/src/cls/IPM/General/LockFile/Dependency.cls
@@ -0,0 +1,21 @@
+/// Class which defines the schema to be used for a dependency object within a lock file
+Class %IPM.General.LockFile.Dependency Extends (%RegisteredObject, %JSON.Adaptor)
+{
+
+/// Version string for this dependency module
+Property VersionString As %IPM.DataType.VersionString(%JSONFIELDNAME = "version");
+
+/// Name of the repository this module comes from
+Property Repository As %IPM.DataType.RepoName(%JSONFIELDNAME = "repository");
+
+/// Array of transient dependencies required by this dependency module.
+/// key: module name
+/// value: semantic version expression (required by this module)
+///
+/// Example: {
+/// "moduleOne": "^1.0.0",
+/// "moduleTwo": "^2.1.3"
+/// }
+Property Dependencies As array Of %IPM.DataType.VersionString(%JSONFIELDNAME = "dependencies");
+
+}
diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls
index db2b1f69..8734ec9a 100644
--- a/src/cls/IPM/Main.cls
+++ b/src/cls/IPM/Main.cls
@@ -163,6 +163,7 @@ This command is an alias for `module-action module-name publish`
+
@@ -290,6 +291,7 @@ load C:\module\root\path -env C:\path\to\env1.json;C:\path\to\env2.json
+
@@ -416,6 +418,7 @@ install -env /path/to/env1.json;/path/to/env2.json example-package
+
diff --git a/src/cls/IPM/Repo/Definition.cls b/src/cls/IPM/Repo/Definition.cls
index cb32b8af..b36b3b50 100644
--- a/src/cls/IPM/Repo/Definition.cls
+++ b/src/cls/IPM/Repo/Definition.cls
@@ -1,6 +1,6 @@
Include (%syPrompt, %IPM.Common)
-Class %IPM.Repo.Definition Extends (%Persistent, %ZEN.DataModel.Adaptor, %IPM.CLI.Commands) [ Abstract ]
+Class %IPM.Repo.Definition Extends (%Persistent, %ZEN.DataModel.Adaptor, %IPM.CLI.Commands, %JSON.Adaptor) [ Abstract ]
{
Parameter DEFAULTGLOBAL = "^IPM.Repo.Definition";
@@ -21,6 +21,9 @@ Parameter MaxDisplayTabCount As INTEGER = 3;
Index ServerDefinitionKey On Name [ Unique ];
+/// String "type" identifier used in lock files
+Property LockFileType As %String(MAXLEN = 100) [ Calculated, ReadOnly ];
+
Property Name As %IPM.DataType.RepoName [ Required ];
Property Enabled As %Boolean [ InitialExpression = 1 ];
@@ -280,6 +283,11 @@ ClassMethod GetOne(
quit ""
}
+Method LockFileTypeGet()
+{
+ return ..#MONIKER
+}
+
Storage Default
{
diff --git a/src/cls/IPM/Repo/Filesystem/Definition.cls b/src/cls/IPM/Repo/Filesystem/Definition.cls
index b99b7d67..582b3240 100644
--- a/src/cls/IPM/Repo/Filesystem/Definition.cls
+++ b/src/cls/IPM/Repo/Filesystem/Definition.cls
@@ -357,6 +357,17 @@ ClassMethod ScanDirectory(
quit tSC
}
+XData LockFileMapping
+{
+
+
+
+
+
+
+
+}
+
Storage Default
{
diff --git a/src/cls/IPM/Repo/Oras/Definition.cls b/src/cls/IPM/Repo/Oras/Definition.cls
index aa51a0da..629e1583 100644
--- a/src/cls/IPM/Repo/Oras/Definition.cls
+++ b/src/cls/IPM/Repo/Oras/Definition.cls
@@ -140,6 +140,17 @@ Method GetPublishingManager(ByRef status)
return ##class(%IPM.Repo.Oras.PublishManager).%Get(.status)
}
+XData LockFileMapping
+{
+
+
+
+
+
+
+
+}
+
Storage Default
{
diff --git a/src/cls/IPM/Repo/Remote/Definition.cls b/src/cls/IPM/Repo/Remote/Definition.cls
index 1e4b7cdd..d259b39a 100644
--- a/src/cls/IPM/Repo/Remote/Definition.cls
+++ b/src/cls/IPM/Repo/Remote/Definition.cls
@@ -132,6 +132,21 @@ Method GetPublishingManager(ByRef status)
return ##class(%IPM.Repo.Remote.PublishManager).%Get(.status)
}
+Method LockFileTypeGet()
+{
+ return ..#MONIKERALIAS
+}
+
+XData LockFileMapping
+{
+
+
+
+
+
+
+}
+
Storage Default
{
diff --git a/src/cls/IPM/Storage/Module.cls b/src/cls/IPM/Storage/Module.cls
index f512d430..e0dcc8b8 100644
--- a/src/cls/IPM/Storage/Module.cls
+++ b/src/cls/IPM/Storage/Module.cls
@@ -11,7 +11,7 @@ Index Name On Name [ Unique ];
Property GlobalScope As %Boolean;
-Property VersionString As %String(MAXLEN = 100, XMLNAME = "Version") [ InitialExpression = "0.0.1+snapshot", Required ];
+Property VersionString As %IPM.DataType.VersionString(XMLNAME = "Version") [ InitialExpression = "0.0.1+snapshot", Required ];
/// Does not need comparison method to be code generated because that comparing VersionString is good enough.
Property Version As %IPM.General.SemanticVersion(ForceCodeGenerate = 0, XMLPROJECTION = "NONE") [ Required ];
diff --git a/src/cls/IPM/Utils/Module.cls b/src/cls/IPM/Utils/Module.cls
index 62b89303..cf39ef63 100644
--- a/src/cls/IPM/Utils/Module.cls
+++ b/src/cls/IPM/Utils/Module.cls
@@ -1182,6 +1182,10 @@ ClassMethod LoadNewModule(
$$$ThrowOnError(##class(%IPM.Storage.Module).CheckSystemRequirements(tModuleName))
kill params("ModuleName") // Ensure module name is not passed down to dependency loads
+ // If lock file is to be created, add the module name to params so lock file is just created for base module and not any of the dependency modules loaded
+ if $get(params("CreateLockFile"), 0) && '$data(params("LockFileModule")){
+ set params("LockFileModule") = tModule.Name
+ }
do ..LoadDependencies(tModule, .params)
set tSC = $system.OBJ.Load(pDirectory_"module.xml",$select(tVerbose:"d",1:"-d"),,.tLoadedList)
@@ -1254,6 +1258,15 @@ ClassMethod LoadDependencies(
set sc = ..LoadModuleReference(moduleReference.ServerName, moduleReference.Name, moduleReference.VersionString,$get(tDeployed), $get(tPlatformVersion), .pParams)
$$$ThrowOnError(sc)
}
+
+ // Create lock file if specified for this module
+ if $get(pParams("CreateLockFile"), 0) && (pModule.Name = $get(pParams("LockFileModule"))) {
+ try {
+ do ##class(%IPM.General.LockFile).CreateLockFileForModule(pModule, flatDepList, .tParams)
+ } catch (ex) {
+ write !, $$$FormatText("Error creating lock file for %1 - %2", pModule.Name, ex.DisplayString()), !
+ }
+ }
}
/// Construct an inverted dependency graph from the dependency graph of a module.
diff --git a/tests/integration_tests/Test/PM/Integration/LockFile.cls b/tests/integration_tests/Test/PM/Integration/LockFile.cls
new file mode 100644
index 00000000..1d981604
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/LockFile.cls
@@ -0,0 +1,322 @@
+Class Test.PM.Integration.LockFile Extends Test.PM.Integration.Base
+{
+
+/// Name of the JSON lock file
+Parameter LockFileName As String = "module-lock.json";
+
+/// Directory where files for expected lock file contents are located
+Parameter ExpectedFilesDir As String = "/home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/";
+
+/// Module with 0 dependencies
+Parameter ModuleA As String = "lock-mod-a-no-deps";
+
+/// Module with 0 dependencies
+Parameter ModuleB As String = "lock-mod-b-no-deps";
+
+/// Module with 2 deps w/o transient (A & B)
+Parameter ModuleC As String = "lock-mod-c-2-deps-0-transient";
+
+/// Module with 1 dep w/ 2 transient (C)
+Parameter ModuleD As String = "lock-mod-d-1-dep-2-transient";
+
+/// Module with 1 dep w/o transient (A)
+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)
+Parameter ModuleG As String = "lock-mod-g-all-repo-types";
+
+/// Module with multiple versions
+/// - v2: Base equivalent to Module F
+/// - v3: Adds dependency Module A
+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";
+
+/// Module with complex set of nested dependencies
+/// Dependencies D, E, & F
+/// - D -> C -> A & B
+/// - E -> A
+/// - F -> E & B
+Parameter ModuleL As String = "lock-mod-l-complex-deps";
+
+Method OnBeforeAllTests() As %Status
+{
+ // Setup repos for LockFile tests
+ set sc = ##class(%IPM.Main).Shell("repo -delete-all")
+ do $$$AssertStatusOK(sc,"Deleted all leftover repos from other tests successfully.")
+ set sc = ##class(%IPM.Main).Shell("repo -n lock-file-base -fs -path /home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/")
+ do $$$AssertStatusOK(sc,"Created lock-file-base repo successfully.")
+ set sc = ##class(%IPM.Main).Shell("repo -n lock-file-edge -fs -path /home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/")
+ do $$$AssertStatusOK(sc,"Created lock-file-edge repo successfully.")
+ return sc
+}
+
+Method OnAfterAllTests() As %Status
+{
+ // Delete repos for LockFile tests
+ set sc = ##class(%IPM.Main).Shell("repo -delete -name lock-file-base")
+ do $$$AssertStatusOK(sc,"Removed lock-file-base repo successfully.")
+ set sc = ##class(%IPM.Main).Shell("repo -delete -name lock-file-edge")
+ do $$$AssertStatusOK(sc,"Removed lock-file-edge repo successfully.")
+ return sc
+}
+
+Method OnAfterOneTest() As %Status
+{
+ // Make test environment clean by uninstalling all modules at the end of each test
+ set sc = ##class(%IPM.Main).Shell("uninstall -all")
+ do $$$AssertStatusOK(sc, "Successfully uninstalled all modules after test")
+ return sc
+}
+
+/// Module with 0 dependencies
+/// Uses Module A
+Method Test01Module0Dependencies()
+{
+ try {
+ set moduleName = ..#ModuleA
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module0Dependencies.")
+ }
+}
+
+/// Module with 2 direct dependencies (they themselves don't have deps)
+/// Uses Module C
+Method Test02Module2Dependencies0Transient()
+{
+ try {
+ set moduleName = ..#ModuleC
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies0Transient.")
+ }
+}
+
+/// Module with dependency that has transient dependencies
+/// Uses Module D
+Method Test03Module1Dependency2Transient()
+{
+ try {
+ set moduleName = ..#ModuleD
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module1Dependency2Transient.")
+ }
+}
+
+/// Module with 1 dep (with transient), 1 dep (without transient)
+/// Uses Module F
+Method Test04Module2Dependencies1Transient()
+{
+ try {
+ set moduleName = ..#ModuleF
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ }
+}
+
+/// Edge cases below:
+/// All repository types
+/// Uses Module G
+Method Test05ModuleDependenciesAllRepositoryTypes()
+{
+ set sc = $$$OK
+ try {
+ // The first step is to publish the modules to their specified repo types. Need to add a repo for the initial module files first though
+ set sc = ##class(%IPM.Main).Shell("repo -n lock-file-other-repos -fs -path /home/irisowner/zpm/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/")
+ do $$$AssertStatusOK(sc,"Created lock-file-other-repos repo successfully.")
+
+ // Publish a module to a remote repository
+ set remoteRepo = "lock-file-remote"
+ set remoteMod = "lock-mod-remote"
+ do ##class(%IPM.Main).Shell("install "_remoteMod)
+ // Add remote repo for this test stack and push the module there
+ set sc = ##class(%IPM.Main).Shell("repo -r -name "_remoteRepo_" -url http://registry:52773/registry -username admin -password SYS")
+ do $$$AssertStatusOK(sc, "Successfully set up remote repo")
+ 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)
+
+ // Publish a module to an ORAS repository
+ set orasRepo = "lock-file-oras"
+ set orasMod = "lock-mod-oras"
+ do ##class(%IPM.Main).Shell("install "_orasMod)
+ // Add oras repo for this test stack and push the module there
+ set sc = ##class(%IPM.Main).Shell("repo -o -name "_orasRepo_" -url http://oras:5000 -publish 1")
+ do $$$AssertStatusOK(sc, "Successfully set up ORAS repo")
+ set sc = ##class(%IPM.Main).Shell("publish "_orasMod_" -r "_orasRepo_" -v -export-deps 1")
+ do $$$AssertStatusOK(sc, "Successfully published module to ORAS repo")
+ // Uninstall mod and to then be installed from ORAS repo
+ do ##class(%IPM.Main).Shell("uninstall -r "_orasMod)
+
+ // Remove repo for files to other repo types so we don't pull the filesystem module by accident
+ 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)
+
+ // 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()
+ }
+}
+
+/// Module with multiple versions: Can both specify a version and defaults to latest (highest #) version
+/// Uses Module H
+Method Test06ModuleMultipleVersions()
+{
+ try {
+ set moduleName = ..#ModuleH
+
+ // First test with the latest version of the module
+ set latestVersion = "3.0.0"
+ set latestLockFileName = "lock-mod-h-multiple-versions-v3.json"
+
+ // Do an initial install of the latest version of the module and create a lock file for it
+ set sc = ##class(%IPM.Main).Shell("install -create-lockfile "_moduleName)
+ do $$$AssertStatusOK(sc, "Able to install "_moduleName_" v"_latestVersion_" and create lock file for it")
+
+ // Compare the generated lock file with the expected one
+ set module = ##class(%IPM.Storage.Module).NameOpen(moduleName)
+ set lockFilePath = module.Root_..#LockFileName
+ 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)
+
+
+
+ // Now test with an older version of the same module
+ set olderLockFileName = "lock-mod-h-multiple-versions-v2.json"
+ set olderVersion = "2.0.0"
+
+ // Do an initial install of the module and create a lock file for it
+ set sc = ##class(%IPM.Main).Shell("install -create-lockfile "_moduleName_" "_olderVersion)
+ do $$$AssertStatusOK(sc, "Able to install "_moduleName_" v"_olderVersion_" and create lock file for it")
+
+ // Compare the generated lock file with the expected one
+ set module = ##class(%IPM.Storage.Module).NameOpen(moduleName)
+ set lockFilePath = module.Root_..#LockFileName
+ set areLockFilesEqual = ..AreLockFilesEqual(lockFilePath, ..#ExpectedFilesDir_olderLockFileName)
+ 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)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ }
+}
+
+/// 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
+/// Uses Module I
+Method Test07ModuleNoLockFile()
+{
+ try {
+ set moduleName = ..#ModuleI
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+
+ // Go back to the initial state by deleting lock file for this module at the end of the test
+ set module = ##class(%IPM.Storage.Module).NameOpen(moduleName)
+ set lockFilePath = module.Root_..#LockFileName
+ if '##class(%File).Delete(lockFilePath) {
+ $$$ThrowStatus($$$ERROR($$$GeneralError,$$$FormatText("Failed to delete lock file located at: %1", lockFilePath)))
+ }
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2Dependencies1Transient.")
+ }
+}
+
+/// 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()
+{
+ try {
+ set moduleName = ..#ModuleK
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in Module2DependenciesSameTransient.")
+ }
+}
+
+/// Tests writing and installing from a lock file for a module with a more complex nested dependencies setup
+/// Uses Module L
+Method Test10ComplexNestedDependencies()
+{
+ try {
+ set moduleName = ..#ModuleL
+ do ..AssertInstallCreatesLockFileAsExpected(moduleName)
+ } catch e {
+ do $$$AssertStatusOK(e.AsStatus(),"An exception occurred in TestComplexNestedDependencies.")
+ }
+}
+
+/// Base cases of creating and installing from a lock file
+Method AssertInstallCreatesLockFileAsExpected(moduleName As %String)
+{
+ // Do an initial install of the module and create a lock file for it
+ set sc = ##class(%IPM.Main).Shell("install -create-lockfile "_moduleName)
+ do $$$AssertStatusOK(sc, "Able to install "_moduleName_" and create lock file for it")
+
+ // Compare the generated lock file with the expected one
+ set module = ##class(%IPM.Storage.Module).NameOpen(moduleName)
+ set lockFilePath = module.Root_..#LockFileName
+ set areLockFilesEqual = ..AreLockFilesEqual(lockFilePath, ..#ExpectedFilesDir_moduleName_".json")
+ do $$$AssertTrue(areLockFilesEqual, "Lock file contents for "_moduleName_" match expected values")
+}
+
+/// Takes in the path to two lock files and compares their values
+/// Comparison is done as a string so if the keys are in a different order the comparison will fail, even if data is equivalent
+/// This is intended as the keys being in a different order should alert us to further investigate
+/// Especially for the dependencies list where order is explicitly required (for zpm "ci" to install in correct order)
+///
+/// Returns: 1 - if lock files have equivalent contents
+/// 0 - if lock files are different
+ClassMethod AreLockFilesEqual(
+ actualLockFilePath As %String,
+ expectedLockFilePath As %String) As %Boolean
+{
+ try {
+ 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()
+ }
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-a-no-deps.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-a-no-deps.json
new file mode 100644
index 00000000..8db64f51
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-a-no-deps.json
@@ -0,0 +1,14 @@
+{
+ "name": "lock-mod-a-no-deps",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-b-no-deps-.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-b-no-deps-.json
new file mode 100644
index 00000000..d0100848
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-b-no-deps-.json
@@ -0,0 +1,14 @@
+{
+ "name": "lock-mod-b-no-deps",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-c-2-deps-0-transient.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-c-2-deps-0-transient.json
new file mode 100644
index 00000000..0dc50fb2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-c-2-deps-0-transient.json
@@ -0,0 +1,24 @@
+{
+ "name": "lock-mod-c-2-deps-0-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-d-1-dep-2-transient.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-d-1-dep-2-transient.json
new file mode 100644
index 00000000..2c72c120
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-d-1-dep-2-transient.json
@@ -0,0 +1,32 @@
+{
+ "name": "lock-mod-d-1-dep-2-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-e-1-dep-0-transient.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-e-1-dep-0-transient.json
new file mode 100644
index 00000000..43d702ad
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-e-1-dep-0-transient.json
@@ -0,0 +1,20 @@
+{
+ "name": "lock-mod-e-1-dep-0-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-f-2-deps-1-transient.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-f-2-deps-1-transient.json
new file mode 100644
index 00000000..4453debe
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-f-2-deps-1-transient.json
@@ -0,0 +1,31 @@
+{
+ "name": "lock-mod-f-2-deps-1-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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-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/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
new file mode 100644
index 00000000..f9c1353b
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-g-all-repo-types.json
@@ -0,0 +1,14 @@
+{
+ "name": "lock-mod-g-all-repo-types",
+ "version": "1.0.0",
+ "repository": "lock-file-edge",
+ "lockFileVersion": "1",
+ "repositories": {},
+ "dependencies": {
+ "lock-mod-remote": {},
+ "lock-mod-oras": {},
+ "lock-mod-h-multiple-versionsttp": {},
+ "lock-mod-f-2-deps-1-transientilesystem": {},
+ "lock-mod-perforce": {}
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-h-multiple-versions-v2.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-h-multiple-versions-v2.json
new file mode 100644
index 00000000..39b50f40
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-h-multiple-versions-v2.json
@@ -0,0 +1,33 @@
+{
+ "name": "lock-mod-h-multiple-versions",
+ "version": "2.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
+ }
+ },
+ "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/expected-files/lock-mod-h-multiple-versions-v3.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-h-multiple-versions-v3.json
new file mode 100644
index 00000000..58a60f6e
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-h-multiple-versions-v3.json
@@ -0,0 +1,37 @@
+{
+ "name": "lock-mod-h-multiple-versions",
+ "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
+ }
+ },
+ "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-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/expected-files/lock-mod-i-no-prior-lock-file.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-i-no-prior-lock-file.json
new file mode 100644
index 00000000..8636b5a6
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-i-no-prior-lock-file.json
@@ -0,0 +1,37 @@
+{
+ "name": "lock-mod-i-no-prior-lock-file",
+ "version": "1.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
+ }
+ },
+ "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-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/expected-files/lock-mod-k-common-transient.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-k-common-transient.json
new file mode 100644
index 00000000..de0a78ba
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-k-common-transient.json
@@ -0,0 +1,45 @@
+{
+ "name": "lock-mod-k-common-transient",
+ "version": "1.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
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-l-complex-deps.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-l-complex-deps.json
new file mode 100644
index 00000000..c62687e5
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-l-complex-deps.json
@@ -0,0 +1,60 @@
+{
+ "name": "lock-mod-l-complex-deps",
+ "version": "1.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
+ }
+ },
+ "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-d-1-dep-2-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-c-2-deps-0-transient": "^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-f-2-deps-1-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-b-no-deps": "^1.0.0",
+ "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
new file mode 100644
index 00000000..4d6c5fc8
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/expected-files/lock-mod-oras.json
@@ -0,0 +1,46 @@
+{
+ "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-remote": {
+ "version": "1.0.0",
+ "repository": "lock-file-remote"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/cls/LockModA/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/cls/LockModA/Class1.cls
new file mode 100644
index 00000000..ae82c785
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/cls/LockModA/Class1.cls
@@ -0,0 +1,9 @@
+Class LockModA.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModA.Class1).MethodA()"
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module-lock.json
new file mode 100644
index 00000000..8db64f51
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module-lock.json
@@ -0,0 +1,14 @@
+{
+ "name": "lock-mod-a-no-deps",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module.xml
new file mode 100644
index 00000000..b116b90f
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-a-no-deps/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ 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-base-cases/lock-mod-b-no-deps/cls/LockModB/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/cls/LockModB/Class1.cls
new file mode 100644
index 00000000..46e089b2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/cls/LockModB/Class1.cls
@@ -0,0 +1,9 @@
+Class LockModB.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModB.Class1).MethodA()"
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module-lock.json
new file mode 100644
index 00000000..d0100848
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module-lock.json
@@ -0,0 +1,14 @@
+{
+ "name": "lock-mod-b-no-deps",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module.xml
new file mode 100644
index 00000000..377f7c92
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-b-no-deps/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ lock-mod-b-no-deps
+ 1.0.0
+
+
+
+
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/cls/LockModC/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/cls/LockModC/Class1.cls
new file mode 100644
index 00000000..3fd2deb8
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/cls/LockModC/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModC.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModC.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModA, LockModB)"
+ do ##class(LockModA.Class1).MethodA()
+ do ##class(LockModB.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module-lock.json
new file mode 100644
index 00000000..0dc50fb2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module-lock.json
@@ -0,0 +1,24 @@
+{
+ "name": "lock-mod-c-2-deps-0-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module.xml
new file mode 100644
index 00000000..7c9633d8
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-c-2-deps-0-transient/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-c-2-deps-0-transient
+ 1.0.0
+
+
+
+ lock-mod-a-no-deps
+ ^1.0.0
+
+
+ lock-mod-b-no-deps
+ ^1.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/cls/LockModD/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/cls/LockModD/Class1.cls
new file mode 100644
index 00000000..150d62f3
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/cls/LockModD/Class1.cls
@@ -0,0 +1,12 @@
+Class LockModD.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModD.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-base-cases/lock-mod-d-1-dep-2-transient/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/module-lock.json
new file mode 100644
index 00000000..2c72c120
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/module-lock.json
@@ -0,0 +1,32 @@
+{
+ "name": "lock-mod-d-1-dep-2-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/module.xml
new file mode 100644
index 00000000..260881e2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-d-1-dep-2-transient/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ lock-mod-d-1-dep-2-transient
+ 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-base-cases/lock-mod-e-1-dep-0-transient/cls/LockModE/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/cls/LockModE/Class1.cls
new file mode 100644
index 00000000..bbd176e2
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/cls/LockModE/Class1.cls
@@ -0,0 +1,12 @@
+Class LockModE.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModE.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModA)"
+ do ##class(LockModA.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module-lock.json
new file mode 100644
index 00000000..43d702ad
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module-lock.json
@@ -0,0 +1,20 @@
+{
+ "name": "lock-mod-e-1-dep-0-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "dependencies": {
+ "lock-mod-a-no-deps": {
+ "version": "1.0.0",
+ "repository": "lock-file-base"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module.xml
new file mode 100644
index 00000000..cf6b9266
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-e-1-dep-0-transient/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ lock-mod-e-1-dep-0-transient
+ 1.0.0
+
+
+
+ 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-base-cases/lock-mod-f-2-deps-1-transient/cls/LockModF/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/cls/LockModF/Class1.cls
new file mode 100644
index 00000000..e2622528
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/cls/LockModF/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModF.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModF.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModB, LockModE)"
+ do ##class(LockModB.Class1).MethodA()
+ do ##class(LockModE.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/module-lock.json
new file mode 100644
index 00000000..4453debe
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/module-lock.json
@@ -0,0 +1,31 @@
+{
+ "name": "lock-mod-f-2-deps-1-transient",
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "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
+ }
+ },
+ "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-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-base-cases/lock-mod-f-2-deps-1-transient/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/module.xml
new file mode 100644
index 00000000..5e19ee6d
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-base-cases/lock-mod-f-2-deps-1-transient/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-f-2-deps-1-transient
+ 1.0.0
+
+
+
+ lock-mod-b-no-deps
+ ^1.0.0
+
+
+ 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/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
new file mode 100644
index 00000000..c39de71b
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/cls/LockModG/Class1.cls
@@ -0,0 +1,14 @@
+Class LockModG.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModG.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModRemote, LockModORAS, LockModC)"
+ 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.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml
new file mode 100644
index 00000000..31c9fc08
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-g-all-repo-types/module.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ lock-mod-g-all-repo-types
+ 3.0.0
+
+
+
+ lock-mod-remote
+ ^1.0.0
+
+
+ lock-mod-oras
+ ^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-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
new file mode 100644
index 00000000..213d1512
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/cls/LockModH/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModH.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is LockModH v2 ##class(LockModH.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModB, LockModE)"
+ do ##class(LockModB.Class1).MethodA()
+ 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-v2/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/module-lock.json
new file mode 100644
index 00000000..39b50f40
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/module-lock.json
@@ -0,0 +1,33 @@
+{
+ "name": "lock-mod-h-multiple-versions",
+ "version": "2.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
+ }
+ },
+ "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-h-multiple-versions-v2/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/module.xml
new file mode 100644
index 00000000..b98a4194
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v2/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ lock-mod-h-multiple-versions
+ 2.0.0
+
+
+
+ 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-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
new file mode 100644
index 00000000..f0e91ba5
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/cls/LockModH/Class1.cls
@@ -0,0 +1,14 @@
+Class LockModH.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is LockModH v3 ##class(LockModH.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModA, LockModB, LockModE)"
+ do ##class(LockModA.Class1).MethodA()
+ do ##class(LockModB.Class1).MethodA()
+ 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/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/module-lock.json
new file mode 100644
index 00000000..58a60f6e
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/module-lock.json
@@ -0,0 +1,37 @@
+{
+ "name": "lock-mod-h-multiple-versions",
+ "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
+ }
+ },
+ "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-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-h-multiple-versions-v3/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/module.xml
new file mode 100644
index 00000000..03300d8d
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-h-multiple-versions-v3/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-h-multiple-versions
+ 3.0.0
+
+
+
+ lock-mod-e-1-dep-0-transient
+ ^1.0.0
+
+
+ lock-mod-b-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-i-no-prior-lock-file/cls/LockModI/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-i-no-prior-lock-file/cls/LockModI/Class1.cls
new file mode 100644
index 00000000..ca7336ea
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-i-no-prior-lock-file/cls/LockModI/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModI.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModI.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModB, LockModE)"
+ do ##class(LockModB.Class1).MethodA()
+ do ##class(LockModE.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-i-no-prior-lock-file/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-i-no-prior-lock-file/module.xml
new file mode 100644
index 00000000..742dadca
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-i-no-prior-lock-file/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-i-no-prior-lock-file
+ 1.0.0
+
+
+
+ lock-mod-b-no-deps
+ ^1.0.0
+
+
+ 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-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
new file mode 100644
index 00000000..52b1281b
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/cls/LockModJ/Class1.cls
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 00000000..4139d459
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-j-deps-misordered/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ 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-edge-cases/lock-mod-k-common-transient/cls/LockModK/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/cls/LockModK/Class1.cls
new file mode 100644
index 00000000..3bd3ed77
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/cls/LockModK/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModK.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModK.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModC, LockModE)"
+ do ##class(LockModC.Class1).MethodA()
+ do ##class(LockModE.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module-lock.json
new file mode 100644
index 00000000..de0a78ba
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module-lock.json
@@ -0,0 +1,45 @@
+{
+ "name": "lock-mod-k-common-transient",
+ "version": "1.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
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module.xml
new file mode 100644
index 00000000..63c4180c
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-k-common-transient/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-k-common-transient
+ 1.0.0
+
+
+
+ lock-mod-c-2-deps-0-transient
+ ^1.0.0
+
+
+ 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-l-complex-deps/cls/LockModL/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/cls/LockModL/Class1.cls
new file mode 100644
index 00000000..b54875bc
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/cls/LockModL/Class1.cls
@@ -0,0 +1,14 @@
+Class LockModL.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModL.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModD, LockModE, LockModF)"
+ do ##class(LockModD.Class1).MethodA()
+ do ##class(LockModE.Class1).MethodA()
+ do ##class(LockModF.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/module-lock.json b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/module-lock.json
new file mode 100644
index 00000000..c62687e5
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/module-lock.json
@@ -0,0 +1,60 @@
+{
+ "name": "lock-mod-l-complex-deps",
+ "version": "1.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
+ }
+ },
+ "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-d-1-dep-2-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-c-2-deps-0-transient": "^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-f-2-deps-1-transient": {
+ "version": "1.0.0",
+ "repository": "lock-file-base",
+ "dependencies": {
+ "lock-mod-b-no-deps": "^1.0.0",
+ "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-l-complex-deps/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/module.xml
new file mode 100644
index 00000000..9378d893
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-edge-cases/lock-mod-l-complex-deps/module.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ lock-mod-l-complex-deps
+ 1.0.0
+
+
+
+ lock-mod-d-1-dep-2-transient
+ ^1.0.0
+
+
+ lock-mod-e-1-dep-0-transient
+ ^1.0.0
+
+
+ lock-mod-f-2-deps-1-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/cls/LockModORAS/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/cls/LockModORAS/Class1.cls
new file mode 100644
index 00000000..f7c4c5a5
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/cls/LockModORAS/Class1.cls
@@ -0,0 +1,13 @@
+Class LockModORAS.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModORAS.Class1).MethodA()"
+
+ write !, "Now calling dependency classes (LockModRemote, LockModC)"
+ do ##class(LockModRemote.Class1).MethodA()
+ do ##class(LockModC.Class1).MethodA()
+}
+
+}
diff --git a/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module.xml b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module.xml
new file mode 100644
index 00000000..3a72a6ed
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-oras/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ lock-mod-oras
+ 1.0.0
+
+
+
+ lock-mod-remote
+ ^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-remote/cls/LockModRemote/Class1.cls b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/cls/LockModRemote/Class1.cls
new file mode 100644
index 00000000..903e0e87
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/cls/LockModRemote/Class1.cls
@@ -0,0 +1,9 @@
+Class LockModRemote.Class1
+{
+
+ClassMethod MethodA()
+{
+ write !, "This is ##class(LockModRemote.Class1).MethodA()"
+}
+
+}
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
new file mode 100644
index 00000000..e3f271bf
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/_data/lock-test/mods-other-repos/lock-mod-remote/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ lock-mod-remote
+ 1.0.0
+
+
+
+
\ No newline at end of file