public void TestApply() { UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("A_NODE:NEEDS[someMod]:FOR[somePass]") { { "key1", "value1" }, { "key2", "value2" }, new TestConfigNode("NODE_1") { { "key3", "value3" }, }, new TestConfigNode("NODE_2") { { "key4", "value4" }, }, }); InsertPatch patch = new InsertPatch(urlConfig, "A_NODE", Substitute.For <IPassSpecifier>()); LinkedList <IProtoUrlConfig> databaseConfigs = new LinkedList <IProtoUrlConfig>(); IProtoUrlConfig config1 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig config2 = Substitute.For <IProtoUrlConfig>(); databaseConfigs.AddLast(config1); databaseConfigs.AddLast(config2); patch.Apply(databaseConfigs, Substitute.For <IPatchProgress>(), Substitute.For <IBasicLogger>()); IProtoUrlConfig[] databaseConfigsArray = databaseConfigs.ToArray(); Assert.Equal(3, databaseConfigsArray.Length); Assert.Same(config1, databaseConfigsArray[0]); Assert.Same(config2, databaseConfigsArray[1]); Assert.Same(urlConfig.parent, databaseConfigsArray[2].UrlFile); Assert.Equal("abc/def.cfg", databaseConfigsArray[2].FileUrl); Assert.Equal("A_NODE", databaseConfigsArray[2].NodeType); Assert.Equal("abc/def.cfg/A_NODE", databaseConfigsArray[2].FullUrl); Assert.NotSame(urlConfig.config, databaseConfigsArray[2].Node); Assert.Equal("A_NODE", databaseConfigsArray[2].Node.name); Assert.Equal("A_NODE:NEEDS[someMod]:FOR[somePass]", urlConfig.config.name); // make sure this hasn't been changed Assert.Equal(2, databaseConfigsArray[2].Node.values.Count); Assert.Equal("key1", databaseConfigsArray[2].Node.values[0].name); Assert.Equal("value1", databaseConfigsArray[2].Node.values[0].value); Assert.Equal("key2", databaseConfigsArray[2].Node.values[1].name); Assert.Equal("value2", databaseConfigsArray[2].Node.values[1].value); Assert.Equal(2, databaseConfigsArray[2].Node.nodes.Count); Assert.Equal("NODE_1", databaseConfigsArray[2].Node.nodes[0].name); Assert.Equal(1, databaseConfigsArray[2].Node.nodes[0].values.Count); Assert.Equal("key3", databaseConfigsArray[2].Node.nodes[0].values[0].name); Assert.Equal("value3", databaseConfigsArray[2].Node.nodes[0].values[0].value); Assert.Equal(0, databaseConfigsArray[2].Node.nodes[0].nodes.Count); Assert.Equal("NODE_2", databaseConfigsArray[2].Node.nodes[1].name); Assert.Equal(1, databaseConfigsArray[2].Node.nodes[1].values.Count); Assert.Equal("key4", databaseConfigsArray[2].Node.nodes[1].values[0].name); Assert.Equal("value4", databaseConfigsArray[2].Node.nodes[1].values[0].value); Assert.Equal(0, databaseConfigsArray[2].Node.nodes[1].nodes.Count); }
public void TestCompilePatch__Copy() { ProtoPatch protoPatch = new ProtoPatch( UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") { { "@name", "boo" }, { "@bar", "bleh" }, }), Command.Copy, "NODE", "foo", null, "#bar", Substitute.For <IPassSpecifier>() ); CopyPatch patch = Assert.IsType <CopyPatch>(patchCompiler.CompilePatch(protoPatch)); Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); ConfigNode config = new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, }; IProtoUrlConfig urlConfig = Substitute.For <IProtoUrlConfig>(); urlConfig.Node.Returns(config); urlConfig.UrlFile.Returns(file); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig); patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingCopy(urlConfig, protoPatch.urlConfig); IProtoUrlConfig[] newConfigs = configs.ToArray(); Assert.Equal(2, newConfigs.Length); Assert.Same(config, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, }, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "boo" }, { "bar", "bleh" }, }, newConfigs[1].Node); Assert.Same(file, newConfigs[1].UrlFile); }
public void TestApply() { ConfigNode config1 = new ConfigNode("NODE"); ConfigNode config2 = new ConfigNode("NODE"); ConfigNode config3 = new ConfigNode("NODE"); ConfigNode config4 = new ConfigNode("NODE"); INodeMatcher nodeMatcher = Substitute.For <INodeMatcher>(); nodeMatcher.IsMatch(config1).Returns(false); nodeMatcher.IsMatch(config2).Returns(true); nodeMatcher.IsMatch(config3).Returns(false); nodeMatcher.IsMatch(config4).Returns(true); DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!NODE")), nodeMatcher, Substitute.For <IPassSpecifier>()); IProtoUrlConfig urlConfig1 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig2 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig3 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig4 = Substitute.For <IProtoUrlConfig>(); urlConfig1.Node.Returns(config1); urlConfig2.Node.Returns(config2); urlConfig3.Node.Returns(config3); urlConfig4.Node.Returns(config4); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig1); configs.AddLast(urlConfig2); configs.AddLast(urlConfig3); configs.AddLast(urlConfig4); IPatchProgress progress = Substitute.For <IPatchProgress>(); IBasicLogger logger = Substitute.For <IBasicLogger>(); patch.Apply(configs, progress, logger); Assert.Equal(new[] { urlConfig1, urlConfig3 }, configs); Received.InOrder(delegate { progress.ApplyingDelete(urlConfig2, patch.UrlConfig); progress.ApplyingDelete(urlConfig4, patch.UrlConfig); }); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); }
public void TestCompilePatch__Edit() { ProtoPatch protoPatch = new ProtoPatch( UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@bar", "bleh" }, }), Command.Edit, "NODE", "foo", null, "#bar", Substitute.For <IPassSpecifier>() ); EditPatch patch = Assert.IsType <EditPatch>(patchCompiler.CompilePatch(protoPatch)); Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); ConfigNode config = new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, }; IProtoUrlConfig urlConfig = Substitute.For <IProtoUrlConfig>(); urlConfig.Node.Returns(config); urlConfig.UrlFile.Returns(file); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig); patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingUpdate(urlConfig, protoPatch.urlConfig); Assert.Single(configs); Assert.NotSame(config, configs.First.Value.Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "bleh" }, }, configs.First.Value.Node); Assert.Same(file, configs.First.Value.UrlFile); }
public void TestApply__NameNotChanged() { ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, }; INodeMatcher nodeMatcher = Substitute.For <INodeMatcher>(); nodeMatcher.IsMatch(config).Returns(true); CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") { { "@foo", "baz" }, { "pqr", "stw" }, }), nodeMatcher, Substitute.For <IPassSpecifier>()); IProtoUrlConfig protoConfig = Substitute.For <IProtoUrlConfig>(); protoConfig.Node.Returns(config); protoConfig.FullUrl.Returns("abc/def.cfg/NODE"); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(protoConfig); IPatchProgress progress = Substitute.For <IPatchProgress>(); IBasicLogger logger = Substitute.For <IBasicLogger>(); patch.Apply(configs, progress, logger); Assert.Single(configs); Assert.Same(protoConfig, configs.First.Value); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, }, configs.First.Value.Node); progress.Received().Error(patch.UrlConfig, "Error - when applying copy ghi/jkl/+NODE to abc/def.cfg/NODE - the copy needs to have a different name than the parent (use @name = xxx)"); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); }
public void Apply(LinkedList <IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger) { if (databaseConfigs == null) { throw new ArgumentNullException(nameof(databaseConfigs)); } if (progress == null) { throw new ArgumentNullException(nameof(progress)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress); for (LinkedListNode <IProtoUrlConfig> listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next) { IProtoUrlConfig protoConfig = listNode.Value; try { if (!NodeMatcher.IsMatch(protoConfig.Node)) { continue; } if (loop) { logger.Info($"Looping on {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}"); } do { progress.ApplyingUpdate(protoConfig, UrlConfig); listNode.Value = protoConfig = new ProtoUrlConfig(protoConfig.UrlFile, MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context)); } while (loop && NodeMatcher.IsMatch(protoConfig.Node)); if (loop) { protoConfig.Node.RemoveNodes("MM_PATCH_LOOP"); } } catch (Exception ex) { progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex); } } }
public void TesApplyingDelete() { IProtoUrlConfig original = Substitute.For <IProtoUrlConfig>(); original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("!SOME_NODE")); Assert.Equal(0, progress.Counter.patchedNodes); progress.ApplyingDelete(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingDelete(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); }
public void TesApplyingCopy() { IProtoUrlConfig original = Substitute.For <IProtoUrlConfig>(); original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("+SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("+SOME_NODE")); Assert.Equal(0, progress.Counter.patchedNodes); progress.ApplyingCopy(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); logger.AssertInfo("Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingCopy(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); logger.AssertInfo("Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); }
public void Test__Constructor__Nested() { IBasicLogger logger2 = Substitute.For <IBasicLogger>(); PatchProgress progress2 = new PatchProgress(progress, logger2); Assert.Same(progress.Counter, progress2.Counter); Assert.Equal(0, progress.Counter.patchedNodes); IProtoUrlConfig original = Substitute.For <IProtoUrlConfig>(); original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); progress2.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); }
public void Apply(LinkedList <IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger) { if (databaseConfigs == null) { throw new ArgumentNullException(nameof(databaseConfigs)); } if (progress == null) { throw new ArgumentNullException(nameof(progress)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress); for (LinkedListNode <IProtoUrlConfig> listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next) { IProtoUrlConfig protoConfig = listNode.Value; try { if (!NodeMatcher.IsMatch(protoConfig.Node)) { continue; } ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context); if (protoConfig.Node.GetValue("name") is string name && name == clone.GetValue("name")) { progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {protoConfig.FullUrl} - the copy needs to have a different name than the parent (use @name = xxx)"); } else { progress.ApplyingCopy(protoConfig, UrlConfig); listNode = databaseConfigs.AddAfter(listNode, new ProtoUrlConfig(protoConfig.UrlFile, clone)); } }
public void TestCompilePatch__Delete() { ProtoPatch protoPatch = new ProtoPatch( UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("-NODE")), Command.Delete, "NODE", "foo", null, "#bar", Substitute.For <IPassSpecifier>() ); DeletePatch patch = Assert.IsType <DeletePatch>(patchCompiler.CompilePatch(protoPatch)); Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); IProtoUrlConfig urlConfig = Substitute.For <IProtoUrlConfig>(); urlConfig.Node.Returns(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, }); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig); patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingDelete(urlConfig, protoPatch.urlConfig); Assert.Empty(configs); }
public void Apply(LinkedList <IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger) { if (databaseConfigs == null) { throw new ArgumentNullException(nameof(databaseConfigs)); } if (progress == null) { throw new ArgumentNullException(nameof(progress)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } LinkedListNode <IProtoUrlConfig> currentNode = databaseConfigs.First; while (currentNode != null) { IProtoUrlConfig protoConfig = currentNode.Value; try { LinkedListNode <IProtoUrlConfig> nextNode = currentNode.Next; if (NodeMatcher.IsMatch(protoConfig.Node)) { progress.ApplyingDelete(protoConfig, UrlConfig); databaseConfigs.Remove(currentNode); } currentNode = nextNode; } catch (Exception ex) { progress.Exception(UrlConfig, $"Exception while applying delete {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex); } } }
public void TestApply() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); ConfigNode config1 = new TestConfigNode("NODE") { { "foo", "bar" }, }; ConfigNode config2 = new TestConfigNode("NODE") { { "foo", "bar" }, }; ConfigNode config3 = new ConfigNode("NODE"); ConfigNode config4 = new ConfigNode("NODE"); INodeMatcher nodeMatcher = Substitute.For <INodeMatcher>(); nodeMatcher.IsMatch(config1).Returns(false); nodeMatcher.IsMatch(config2).Returns(true); nodeMatcher.IsMatch(config3).Returns(false); nodeMatcher.IsMatch(config4).Returns(true); EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@foo", "baz" }, { "pqr", "stw" }, }), nodeMatcher, Substitute.For <IPassSpecifier>()); IProtoUrlConfig urlConfig1 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig2 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig3 = Substitute.For <IProtoUrlConfig>(); IProtoUrlConfig urlConfig4 = Substitute.For <IProtoUrlConfig>(); urlConfig1.Node.Returns(config1); urlConfig2.Node.Returns(config2); urlConfig3.Node.Returns(config3); urlConfig4.Node.Returns(config4); urlConfig1.UrlFile.Returns(file); urlConfig2.UrlFile.Returns(file); urlConfig3.UrlFile.Returns(file); urlConfig4.UrlFile.Returns(file); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig1); configs.AddLast(urlConfig2); configs.AddLast(urlConfig3); configs.AddLast(urlConfig4); IPatchProgress progress = Substitute.For <IPatchProgress>(); IBasicLogger logger = Substitute.For <IBasicLogger>(); patch.Apply(configs, progress, logger); IProtoUrlConfig[] newConfigs = configs.ToArray(); Assert.Equal(4, newConfigs.Length); Assert.Same(urlConfig1, newConfigs[0]); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "bar" }, }, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "baz" }, { "pqr", "stw" }, }, newConfigs[1].Node); Assert.Same(file, newConfigs[1].UrlFile); Assert.Same(urlConfig3, newConfigs[2]); AssertNodesEqual(new ConfigNode("NODE"), newConfigs[2].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "pqr", "stw" }, }, newConfigs[3].Node); Assert.Same(file, newConfigs[3].UrlFile); Received.InOrder(delegate { progress.ApplyingUpdate(urlConfig2, patch.UrlConfig); progress.ApplyingUpdate(urlConfig4, patch.UrlConfig); }); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); }
public void TestApply__Loop() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "aaa", "1" }, }; INodeMatcher nodeMatcher = Substitute.For <INodeMatcher>(); nodeMatcher.IsMatch(Arg.Is <ConfigNode>(node => int.Parse(node.GetValue("aaa")) < 10)).Returns(true); EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@aaa *", "2" }, { "bbb", "002" }, new ConfigNode("MM_PATCH_LOOP"), }), nodeMatcher, Substitute.For <IPassSpecifier>()); IProtoUrlConfig urlConfig = Substitute.For <IProtoUrlConfig>(); urlConfig.Node.Returns(config); urlConfig.UrlFile.Returns(file); urlConfig.FullUrl.Returns("abc/def.cfg/NODE"); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(urlConfig); IPatchProgress progress = Substitute.For <IPatchProgress>(); IBasicLogger logger = Substitute.For <IBasicLogger>(); List <IProtoUrlConfig> modifiedUrlConfigs = new List <IProtoUrlConfig>(); progress.ApplyingUpdate(Arg.Do <IProtoUrlConfig>(url => modifiedUrlConfigs.Add(url)), patch.UrlConfig); patch.Apply(configs, progress, logger); Assert.Single(configs); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, { "aaa", "16" }, { "bbb", "002" }, { "bbb", "002" }, { "bbb", "002" }, { "bbb", "002" }, }, configs.First.Value.Node); Assert.Same(file, configs.First.Value.UrlFile); Assert.Same(urlConfig, modifiedUrlConfigs[0]); Assert.NotSame(urlConfig, modifiedUrlConfigs[1]); Assert.NotSame(urlConfig, modifiedUrlConfigs[2]); Assert.NotSame(urlConfig, modifiedUrlConfigs[3]); Received.InOrder(delegate { logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); progress.ApplyingUpdate(urlConfig, patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[3], patch.UrlConfig); }); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); }
public void TestApply__NameChanged() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, }; INodeMatcher nodeMatcher = Substitute.For <INodeMatcher>(); nodeMatcher.IsMatch(config).Returns(true); CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@name", "001" }, { "@foo", "baz" }, { "pqr", "stw" }, }), nodeMatcher, Substitute.For <IPassSpecifier>()); IProtoUrlConfig protoConfig = Substitute.For <IProtoUrlConfig>(); protoConfig.Node.Returns(config); protoConfig.UrlFile.Returns(file); LinkedList <IProtoUrlConfig> configs = new LinkedList <IProtoUrlConfig>(); configs.AddLast(protoConfig); IPatchProgress progress = Substitute.For <IPatchProgress>(); IBasicLogger logger = Substitute.For <IBasicLogger>(); patch.Apply(configs, progress, logger); IProtoUrlConfig[] newConfigs = configs.ToArray(); Assert.Equal(2, newConfigs.Length); Assert.Same(protoConfig, newConfigs[0]); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, }, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "001" }, { "foo", "baz" }, { "pqr", "stw" }, }, newConfigs[1].Node); Assert.Same(file, newConfigs[1].UrlFile); progress.Received().ApplyingCopy(protoConfig, patch.UrlConfig); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); }