public void TestGetTree()
        {
            var digest1 = new ManifestDigest(sha256New: "a");
            var digest2 = new ManifestDigest(sha256New: "b");

            _storeMock.Setup(x => x.GetPath(digest1)).Returns("fake/path");
            _storeMock.Setup(x => x.GetPath(digest2)).Returns(() => null);

            var tree = _selectionsManager.GetTree(new Selections
            {
                InterfaceUri    = new FeedUri("http://root/"),
                Implementations =
                {
                    new ImplementationSelection
                    {
                        InterfaceUri   = new FeedUri("http://root/"),
                        ID             = "a",
                        ManifestDigest = digest1,
                        Version        = new ImplementationVersion("1.0"),
                        Dependencies   =
                        {
                            new Dependency {
                                InterfaceUri = new FeedUri("http://dependency/")
                            },
                            new Dependency {
                                InterfaceUri = new FeedUri("http://missing/")
                            }
                        }
                    },
                    new ImplementationSelection
                    {
                        InterfaceUri   = new FeedUri("http://dependency/"),
                        ID             = "b",
                        ManifestDigest = digest2,
                        Version        = new ImplementationVersion("2.0"),
                        Dependencies   =   { new Dependency{
                                                 InterfaceUri = new FeedUri("http://root/")
                                             } }                                                     // Exercise cycle detection
                    }
                }
            });

            var node1 = new SelectionsTreeNode(new FeedUri("http://root/"), new ImplementationVersion("1.0"), "fake/path", parent: null);

            node1.ToString().Should().Be("- URI: http://root/\n  Version: 1.0\n  Path: fake/path");

            var node2 = new SelectionsTreeNode(new FeedUri("http://dependency/"), new ImplementationVersion("2.0"), path: null, parent: node1);

            node2.ToString().Should().Be($"  - URI: http://dependency/\n    Version: 2.0\n    {Resources.NotCached}");

            var node3 = new SelectionsTreeNode(new FeedUri("http://missing/"), version: null, path: null, parent: node1);

            node3.ToString().Should().Be($"  - URI: http://missing/\n    {Resources.NoSelectedVersion}");

            tree.Should().Equal(node1, node2, node3);
        }
    /// <inheritdoc/>
    public NamedCollection <SelectionsTreeNode> GetTree(Selections selections)
    {
        #region Sanity checks
        if (selections == null)
        {
            throw new ArgumentNullException(nameof(selections));
        }
        #endregion

        var visited = new HashSet <FeedUri>();
        var result  = new NamedCollection <SelectionsTreeNode>();

        ImplementationSelection?TryGetImplementation(IInterfaceUri target)
        {
            try
            {
                return(selections[target.InterfaceUri]);
            }
            catch (KeyNotFoundException)
            {
                return(null);
            }
        }

        string?GetPath(ImplementationBase implementation)
        => implementation.LocalPath
        ?? (implementation.ID.StartsWith(ExternalImplementation.PackagePrefix)
                   ? "(" + implementation.ID + ")"
                   : _implementationStore.GetPath(implementation.ManifestDigest));

        void AddNodes(IInterfaceUri target, SelectionsTreeNode?parent)
        {
            // Prevent infinite recursion
            if (visited.Contains(target.InterfaceUri))
            {
                return;
            }
            visited.Add(target.InterfaceUri);

            var implementation = TryGetImplementation(target);

            var node = new SelectionsTreeNode(
                target.InterfaceUri,
                implementation?.Version,
                (implementation == null) ? null : GetPath(implementation),
                parent);

            result.Add(node);
            if (implementation == null)
            {
                return;
            }

            // Recurse into regular dependencies
            foreach (var dependency in implementation.Dependencies)
            {
                AddNodes(dependency, parent: node);
            }

            foreach (var command in implementation.Commands)
            {
                // Recurse into command dependencies
                foreach (var dependency in command.Dependencies)
                {
                    AddNodes(dependency, parent: node);
                }

                // Recurse into runner dependency
                if (command.Runner != null)
                {
                    AddNodes(command.Runner, parent: node);
                }
            }
        }

        AddNodes(selections, parent: null);
        return(result);
    }