diff --git a/DistTestCore/Codex/CodexAccess.cs b/DistTestCore/Codex/CodexAccess.cs index a7c826e..ca6953f 100644 --- a/DistTestCore/Codex/CodexAccess.cs +++ b/DistTestCore/Codex/CodexAccess.cs @@ -4,13 +4,13 @@ namespace DistTestCore.Codex { public class CodexAccess { - private readonly RunningContainer runningContainer; - public CodexAccess(RunningContainer runningContainer) { - this.runningContainer = runningContainer; + Container = runningContainer; } + public RunningContainer Container { get; } + public CodexDebugResponse GetDebugInfo() { var response = Http().HttpGetJson("debug/info"); @@ -18,12 +18,27 @@ namespace DistTestCore.Codex return response; } + public string UploadFile(FileStream fileStream) + { + return Http().HttpPostStream("upload", fileStream); + } + + public Stream DownloadFile(string contentId) + { + return Http().HttpGetStream("download/" + contentId); + } + private Http Http() { - var ip = runningContainer.Pod.Cluster.GetIp(); - var port = runningContainer.ServicePorts[0].Number; + var ip = Container.Pod.Cluster.GetIp(); + var port = Container.ServicePorts[0].Number; return new Http(ip, port, baseUrl: "/api/codex/v1"); } + + public string ConnectToPeer(string peerId, string peerMultiAddress) + { + return Http().HttpGetString($"connect/{peerId}?addrs={peerMultiAddress}"); + } } public class CodexDebugResponse diff --git a/DistTestCore/Codex/CodexStartupConfig.cs b/DistTestCore/Codex/CodexStartupConfig.cs index a978b5c..f83d629 100644 --- a/DistTestCore/Codex/CodexStartupConfig.cs +++ b/DistTestCore/Codex/CodexStartupConfig.cs @@ -1,4 +1,6 @@ -namespace DistTestCore.Codex +using KubernetesWorkflow; + +namespace DistTestCore.Codex { public class CodexStartupConfig { diff --git a/DistTestCore/CodexNodeGroup.cs b/DistTestCore/CodexNodeGroup.cs new file mode 100644 index 0000000..4ca3a6c --- /dev/null +++ b/DistTestCore/CodexNodeGroup.cs @@ -0,0 +1,78 @@ +using DistTestCore.Codex; +using KubernetesWorkflow; +using System.Collections; + +namespace DistTestCore +{ + public interface ICodexNodeGroup : IEnumerable + { + //ICodexSetup BringOffline(); + IOnlineCodexNode this[int index] { get; } + } + + public class CodexNodeGroup : ICodexNodeGroup + { + private readonly TestLifecycle lifecycle; + + public CodexNodeGroup(TestLifecycle lifecycle, CodexSetup setup, RunningContainers containers) + { + this.lifecycle = lifecycle; + Setup = setup; + Containers = containers; + Nodes = containers.Containers.Select(c => CreateOnlineCodexNode(c)).ToArray(); + } + + public IOnlineCodexNode this[int index] + { + get + { + return Nodes[index]; + } + } + + //public ICodexSetup BringOffline() + //{ + // //return k8SManager.BringOffline(this); + //} + + public CodexSetup Setup { get; } + public RunningContainers Containers { get; } + public OnlineCodexNode[] Nodes { get; } + + //public GethCompanionGroup? GethCompanionGroup { get; set; } + + //public CodexNodeContainer[] GetContainers() + //{ + // return Nodes.Select(n => n.Container).ToArray(); + //} + + public IEnumerator GetEnumerator() + { + return Nodes.Cast().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Nodes.GetEnumerator(); + } + + //public CodexNodeLog DownloadLog(IOnlineCodexNode node) + //{ + // var logDownloader = new PodLogDownloader(log, k8SManager); + // var n = (OnlineCodexNode)node; + // return logDownloader.DownloadLog(n); + //} + + public string Describe() + { + var orderNumber = Containers.RunningPod.Ip; + return $"CodexNodeGroup@{orderNumber}-{Setup.Describe()}"; + } + + private OnlineCodexNode CreateOnlineCodexNode(RunningContainer c) + { + var access = new CodexAccess(c); + return new OnlineCodexNode(lifecycle, access, this); + } + } +} diff --git a/DistTestCore/CodexSetupConfig.cs b/DistTestCore/CodexSetup.cs similarity index 73% rename from DistTestCore/CodexSetupConfig.cs rename to DistTestCore/CodexSetup.cs index c7dfef8..ddc6ef0 100644 --- a/DistTestCore/CodexSetupConfig.cs +++ b/DistTestCore/CodexSetup.cs @@ -3,24 +3,24 @@ using KubernetesWorkflow; namespace DistTestCore { - public interface ICodexSetupConfig + public interface ICodexSetup { - ICodexSetupConfig At(Location location); - ICodexSetupConfig WithLogLevel(CodexLogLevel level); + ICodexSetup At(Location location); + ICodexSetup WithLogLevel(CodexLogLevel level); //ICodexStartupConfig WithBootstrapNode(IOnlineCodexNode node); - ICodexSetupConfig WithStorageQuota(ByteSize storageQuota); - ICodexSetupConfig EnableMetrics(); + ICodexSetup WithStorageQuota(ByteSize storageQuota); + ICodexSetup EnableMetrics(); //ICodexSetupConfig EnableMarketplace(int initialBalance); ICodexNodeGroup BringOnline(); } - public class CodexSetupConfig : CodexStartupConfig, ICodexSetupConfig + public class CodexSetup : CodexStartupConfig, ICodexSetup { private readonly CodexStarter starter; public int NumberOfNodes { get; } - public CodexSetupConfig(CodexStarter starter, int numberOfNodes) + public CodexSetup(CodexStarter starter, int numberOfNodes) { this.starter = starter; NumberOfNodes = numberOfNodes; @@ -31,7 +31,7 @@ namespace DistTestCore return starter.BringOnline(this); } - public ICodexSetupConfig At(Location location) + public ICodexSetup At(Location location) { Location = location; return this; @@ -43,19 +43,19 @@ namespace DistTestCore // return this; //} - public ICodexSetupConfig WithLogLevel(CodexLogLevel level) + public ICodexSetup WithLogLevel(CodexLogLevel level) { LogLevel = level; return this; } - public ICodexSetupConfig WithStorageQuota(ByteSize storageQuota) + public ICodexSetup WithStorageQuota(ByteSize storageQuota) { StorageQuota = storageQuota; return this; } - public ICodexSetupConfig EnableMetrics() + public ICodexSetup EnableMetrics() { MetricsEnabled = true; return this; diff --git a/DistTestCore/CodexStarter.cs b/DistTestCore/CodexStarter.cs index 0222925..86ed8c3 100644 --- a/DistTestCore/CodexStarter.cs +++ b/DistTestCore/CodexStarter.cs @@ -1,27 +1,28 @@ using DistTestCore.Codex; using KubernetesWorkflow; -using Logging; namespace DistTestCore { public class CodexStarter { private readonly WorkflowCreator workflowCreator; + private readonly TestLifecycle lifecycle; - public CodexStarter(TestLog log, Configuration configuration) + public CodexStarter(TestLifecycle lifecycle, Configuration configuration) { workflowCreator = new WorkflowCreator(configuration.GetK8sConfiguration()); + this.lifecycle = lifecycle; } - public ICodexNodeGroup BringOnline(CodexSetupConfig codexSetupConfig) + public ICodexNodeGroup BringOnline(CodexSetup codexSetup) { var workflow = workflowCreator.CreateWorkflow(); var startupConfig = new StartupConfig(); - startupConfig.Add(codexSetupConfig); + startupConfig.Add(codexSetup); - var runningContainers = workflow.Start(codexSetupConfig.NumberOfNodes, codexSetupConfig.Location, new CodexContainerRecipe(), startupConfig); + var runningContainers = workflow.Start(codexSetup.NumberOfNodes, codexSetup.Location, new CodexContainerRecipe(), startupConfig); - // create access objects. Easy, right? + return new CodexNodeGroup(lifecycle, codexSetup, runningContainers); } public void DeleteAllResources() diff --git a/DistTestCore/DistTest.cs b/DistTestCore/DistTest.cs index 995fffa..9a43a7b 100644 --- a/DistTestCore/DistTest.cs +++ b/DistTestCore/DistTest.cs @@ -61,27 +61,27 @@ namespace DistTestCore return lifecycle.FileManager.GenerateTestFile(size); } - public ICodexSetupConfig SetupCodexNodes(int numberOfNodes) + public ICodexSetup SetupCodexNodes(int numberOfNodes) { - return new CodexSetupConfig(lifecycle.CodexStarter, numberOfNodes); + return new CodexSetup(lifecycle.CodexStarter, numberOfNodes); } private void IncludeLogsAndMetricsOnTestFailure() { - var result = TestContext.CurrentContext.Result; - if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed) - { - if (IsDownloadingLogsAndMetricsEnabled()) - { - log.Log("Downloading all CodexNode logs and metrics because of test failure..."); - k8sManager.ForEachOnlineGroup(DownloadLogs); - k8sManager.DownloadAllMetrics(); - } - else - { - log.Log("Skipping download of all CodexNode logs and metrics due to [DontDownloadLogsAndMetricsOnFailure] attribute."); - } - } + //var result = TestContext.CurrentContext.Result; + //if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed) + //{ + // if (IsDownloadingLogsAndMetricsEnabled()) + // { + // log.Log("Downloading all CodexNode logs and metrics because of test failure..."); + // k8sManager.ForEachOnlineGroup(DownloadLogs); + // k8sManager.DownloadAllMetrics(); + // } + // else + // { + // log.Log("Skipping download of all CodexNode logs and metrics due to [DontDownloadLogsAndMetricsOnFailure] attribute."); + // } + //} } private void Log(string msg) @@ -101,19 +101,19 @@ namespace DistTestCore private void DownloadLogs(CodexNodeGroup group) { - foreach (var node in group) - { - var downloader = new PodLogDownloader(log, k8sManager); - var n = (OnlineCodexNode)node; - downloader.DownloadLog(n); - } + //foreach (var node in group) + //{ + // var downloader = new PodLogDownloader(log, k8sManager); + // var n = (OnlineCodexNode)node; + // downloader.DownloadLog(n); + //} } - private bool IsDownloadingLogsAndMetricsEnabled() - { - var testProperties = TestContext.CurrentContext.Test.Properties; - return !testProperties.ContainsKey(PodLogDownloader.DontDownloadLogsOnFailureKey); - } + //private bool IsDownloadingLogsAndMetricsEnabled() + //{ + // var testProperties = TestContext.CurrentContext.Test.Properties; + // return !testProperties.ContainsKey(PodLogDownloader.DontDownloadLogsOnFailureKey); + //} } public static class GlobalTestFailure diff --git a/DistTestCore/Http.cs b/DistTestCore/Http.cs index 365920b..f75768b 100644 --- a/DistTestCore/Http.cs +++ b/DistTestCore/Http.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using NUnit.Framework; using System.Net.Http.Headers; +using Utils; namespace DistTestCore { @@ -26,8 +27,8 @@ namespace DistTestCore { using var client = GetClient(); var url = GetUrl() + route; - var result = Utils.Wait(client.GetAsync(url)); - return Utils.Wait(result.Content.ReadAsStringAsync()); + var result = Time.Wait(client.GetAsync(url)); + return Time.Wait(result.Content.ReadAsStringAsync()); }); } @@ -45,9 +46,9 @@ namespace DistTestCore var content = new StreamContent(stream); content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); - var response = Utils.Wait(client.PostAsync(url, content)); + var response = Time.Wait(client.PostAsync(url, content)); - return Utils.Wait(response.Content.ReadAsStringAsync()); + return Time.Wait(response.Content.ReadAsStringAsync()); }); } @@ -58,7 +59,7 @@ namespace DistTestCore var client = GetClient(); var url = GetUrl() + route; - return Utils.Wait(client.GetStreamAsync(url)); + return Time.Wait(client.GetStreamAsync(url)); }); } diff --git a/DistTestCore/OnlineCodexNode.cs b/DistTestCore/OnlineCodexNode.cs new file mode 100644 index 0000000..a4082a8 --- /dev/null +++ b/DistTestCore/OnlineCodexNode.cs @@ -0,0 +1,126 @@ +using DistTestCore.Codex; +using NUnit.Framework; + +namespace DistTestCore +{ + public interface IOnlineCodexNode + { + CodexDebugResponse GetDebugInfo(); + ContentId UploadFile(TestFile file); + TestFile? DownloadContent(ContentId contentId); + void ConnectToPeer(IOnlineCodexNode node); + //ICodexNodeLog DownloadLog(); + //IMetricsAccess Metrics { get; } + //IMarketplaceAccess Marketplace { get; } + } + + public class OnlineCodexNode : IOnlineCodexNode + { + private const string SuccessfullyConnectedMessage = "Successfully connected to peer"; + private const string UploadFailedMessage = "Unable to store block"; + private readonly TestLifecycle lifecycle; + + public OnlineCodexNode(TestLifecycle lifecycle, CodexAccess codexAccess, CodexNodeGroup group) + { + this.lifecycle = lifecycle; + CodexAccess = codexAccess; + Group = group; + } + + public CodexAccess CodexAccess { get; } + public CodexNodeGroup Group { get; } + + public string GetName() + { + return $"<{CodexAccess.Container.Recipe.Name}>"; + } + + public CodexDebugResponse GetDebugInfo() + { + var response = CodexAccess.GetDebugInfo(); + Log($"Got DebugInfo with id: '{response.id}'."); + return response; + } + + public ContentId UploadFile(TestFile file) + { + Log($"Uploading file of size {file.GetFileSize()}..."); + using var fileStream = File.OpenRead(file.Filename); + var response = CodexAccess.UploadFile(fileStream); + if (response.StartsWith(UploadFailedMessage)) + { + Assert.Fail("Node failed to store block."); + } + Log($"Uploaded file. Received contentId: '{response}'."); + return new ContentId(response); + } + + public TestFile? DownloadContent(ContentId contentId) + { + Log($"Downloading for contentId: '{contentId.Id}'..."); + var file = lifecycle.FileManager.CreateEmptyTestFile(); + DownloadToFile(contentId.Id, file); + Log($"Downloaded file of size {file.GetFileSize()} to '{file.Filename}'."); + return file; + } + + public void ConnectToPeer(IOnlineCodexNode node) + { + var peer = (OnlineCodexNode)node; + + Log($"Connecting to peer {peer.GetName()}..."); + var peerInfo = node.GetDebugInfo(); + var response = CodexAccess.ConnectToPeer(peerInfo.id, GetPeerMultiAddress(peer, peerInfo)); + + Assert.That(response, Is.EqualTo(SuccessfullyConnectedMessage), "Unable to connect codex nodes."); + Log($"Successfully connected to peer {peer.GetName()}."); + } + + //public ICodexNodeLog DownloadLog() + //{ + // return Group.DownloadLog(this); + //} + + public string Describe() + { + return $"{Group.Describe()} contains {GetName()}"; + } + + private string GetPeerMultiAddress(OnlineCodexNode peer, CodexDebugResponse peerInfo) + { + var multiAddress = peerInfo.addrs.First(); + // Todo: Is there a case where First address in list is not the way? + + if (Group == peer.Group) + { + return multiAddress; + } + + // The peer we want to connect is in a different pod. + // We must replace the default IP with the pod IP in the multiAddress. + return multiAddress.Replace("0.0.0.0", peer.Group.Containers.RunningPod.Ip); + } + + private void DownloadToFile(string contentId, TestFile file) + { + using var fileStream = File.OpenWrite(file.Filename); + using var downloadStream = CodexAccess.DownloadFile(contentId); + downloadStream.CopyTo(fileStream); + } + + private void Log(string msg) + { + lifecycle.Log.Log($"{GetName()}: {msg}"); + } + } + + public class ContentId + { + public ContentId(string id) + { + Id = id; + } + + public string Id { get; } + } +} diff --git a/DistTestCore/TestLifecycle.cs b/DistTestCore/TestLifecycle.cs index 6ff92f9..7f35056 100644 --- a/DistTestCore/TestLifecycle.cs +++ b/DistTestCore/TestLifecycle.cs @@ -8,7 +8,7 @@ namespace DistTestCore { Log = new TestLog(configuration.GetLogConfig()); FileManager = new FileManager(Log, configuration); - CodexStarter = new CodexStarter(Log, configuration); + CodexStarter = new CodexStarter(this, configuration); } public TestLog Log { get; } diff --git a/KubernetesWorkflow/K8sController.cs b/KubernetesWorkflow/K8sController.cs index df68f18..3df4549 100644 --- a/KubernetesWorkflow/K8sController.cs +++ b/KubernetesWorkflow/K8sController.cs @@ -187,7 +187,7 @@ namespace KubernetesWorkflow private string GetNameForPort(ContainerRecipe recipe, Port port) { - return $"P{workflowNumberSource.WorkflowNumber}-{recipe.Number}-{port.Number}"; + return $"p{workflowNumberSource.WorkflowNumber}-{recipe.Number}-{port.Number}"; } #endregion diff --git a/Tests/BasicTests/SimpleTests.cs b/Tests/BasicTests/SimpleTests.cs index e15ce98..f4214e6 100644 --- a/Tests/BasicTests/SimpleTests.cs +++ b/Tests/BasicTests/SimpleTests.cs @@ -1,4 +1,4 @@ -using CodexDistTestCore; +using DistTestCore; using NUnit.Framework; namespace Tests.BasicTests @@ -6,68 +6,6 @@ namespace Tests.BasicTests [TestFixture] public class SimpleTests : DistTest { - [Test] - public void TwoMetricsExample() - { - var group = SetupCodexNodes(2) - .EnableMetrics() - .BringOnline(); - - var group2 = SetupCodexNodes(2) - .EnableMetrics() - .BringOnline(); - - var primary = group[0]; - var secondary = group[1]; - var primary2 = group2[0]; - var secondary2 = group2[1]; - - primary.ConnectToPeer(secondary); - primary2.ConnectToPeer(secondary2); - - Thread.Sleep(TimeSpan.FromMinutes(5)); - - primary.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1)); - primary2.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1)); - } - - [Test] - public void MarketplaceExample() - { - var group = SetupCodexNodes(4) - .WithStorageQuota(10.GB()) - .EnableMarketplace(initialBalance: 20) - .BringOnline(); - - foreach (var node in group) - { - Assert.That(node.Marketplace.GetBalance(), Is.EqualTo(20)); - } - - // WIP: Balance is now only ETH. - // todo: All nodes should have plenty of ETH to pay for transactions. - // todo: Upload our own token, use this exclusively. ETH should be invisibile to the tests. - - - //var secondary = SetupCodexNodes(1) - // .EnableMarketplace(initialBalance: 1000) - // .BringOnline()[0]; - - //primary.ConnectToPeer(secondary); - //primary.Marketplace.MakeStorageAvailable(10.GB(), minPricePerBytePerSecond: 1, maxCollateral: 20); - - //var testFile = GenerateTestFile(10.MB()); - //var contentId = secondary.UploadFile(testFile); - //secondary.Marketplace.RequestStorage(contentId, pricePerBytePerSecond: 2, - // requiredCollateral: 10, minRequiredNumberOfNodes: 1); - - //primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed."); - //var primaryBalance = primary.Marketplace.GetBalance(); - - //secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage."); - //primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage."); - } - [Test] public void OneClientTest() { @@ -82,53 +20,115 @@ namespace Tests.BasicTests testFile.AssertIsEqual(downloadedFile); } - [Test] - public void TwoClientsOnePodTest() - { - var group = SetupCodexNodes(2).BringOnline(); + //[Test] + //public void TwoClientsOnePodTest() + //{ + // var group = SetupCodexNodes(2).BringOnline(); - var primary = group[0]; - var secondary = group[1]; + // var primary = group[0]; + // var secondary = group[1]; - PerformTwoClientTest(primary, secondary); - } + // PerformTwoClientTest(primary, secondary); + //} - [Test] - public void TwoClientsTwoPodsTest() - { - var primary = SetupCodexNodes(1).BringOnline()[0]; + //[Test] + //public void TwoClientsTwoPodsTest() + //{ + // var primary = SetupCodexNodes(1).BringOnline()[0]; - var secondary = SetupCodexNodes(1).BringOnline()[0]; + // var secondary = SetupCodexNodes(1).BringOnline()[0]; - PerformTwoClientTest(primary, secondary); - } + // PerformTwoClientTest(primary, secondary); + //} - [Test] - [Ignore("Requires Location map to be configured for k8s cluster.")] - public void TwoClientsTwoLocationsTest() - { - var primary = SetupCodexNodes(1) - .At(Location.BensLaptop) - .BringOnline()[0]; + //[Test] + //[Ignore("Requires Location map to be configured for k8s cluster.")] + //public void TwoClientsTwoLocationsTest() + //{ + // var primary = SetupCodexNodes(1) + // .At(Location.BensLaptop) + // .BringOnline()[0]; - var secondary = SetupCodexNodes(1) - .At(Location.BensOldGamingMachine) - .BringOnline()[0]; + // var secondary = SetupCodexNodes(1) + // .At(Location.BensOldGamingMachine) + // .BringOnline()[0]; - PerformTwoClientTest(primary, secondary); - } + // PerformTwoClientTest(primary, secondary); + //} - private void PerformTwoClientTest(IOnlineCodexNode primary, IOnlineCodexNode secondary) - { - primary.ConnectToPeer(secondary); + //[Test] + //public void TwoMetricsExample() + //{ + // var group = SetupCodexNodes(2) + // .EnableMetrics() + // .BringOnline(); - var testFile = GenerateTestFile(1.MB()); + // var group2 = SetupCodexNodes(2) + // .EnableMetrics() + // .BringOnline(); - var contentId = primary.UploadFile(testFile); + // var primary = group[0]; + // var secondary = group[1]; + // var primary2 = group2[0]; + // var secondary2 = group2[1]; - var downloadedFile = secondary.DownloadContent(contentId); + // primary.ConnectToPeer(secondary); + // primary2.ConnectToPeer(secondary2); - testFile.AssertIsEqual(downloadedFile); - } + // Thread.Sleep(TimeSpan.FromMinutes(5)); + + // primary.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1)); + // primary2.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1)); + //} + + //[Test] + //public void MarketplaceExample() + //{ + // var group = SetupCodexNodes(4) + // .WithStorageQuota(10.GB()) + // .EnableMarketplace(initialBalance: 20) + // .BringOnline(); + + // foreach (var node in group) + // { + // Assert.That(node.Marketplace.GetBalance(), Is.EqualTo(20)); + // } + + // // WIP: Balance is now only ETH. + // // todo: All nodes should have plenty of ETH to pay for transactions. + // // todo: Upload our own token, use this exclusively. ETH should be invisibile to the tests. + + + // //var secondary = SetupCodexNodes(1) + // // .EnableMarketplace(initialBalance: 1000) + // // .BringOnline()[0]; + + // //primary.ConnectToPeer(secondary); + // //primary.Marketplace.MakeStorageAvailable(10.GB(), minPricePerBytePerSecond: 1, maxCollateral: 20); + + // //var testFile = GenerateTestFile(10.MB()); + // //var contentId = secondary.UploadFile(testFile); + // //secondary.Marketplace.RequestStorage(contentId, pricePerBytePerSecond: 2, + // // requiredCollateral: 10, minRequiredNumberOfNodes: 1); + + // //primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed."); + // //var primaryBalance = primary.Marketplace.GetBalance(); + + // //secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage."); + // //primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage."); + //} + + //private void PerformTwoClientTest(IOnlineCodexNode primary, IOnlineCodexNode secondary) + //{ + // primary.ConnectToPeer(secondary); + + // var testFile = GenerateTestFile(1.MB()); + + // var contentId = primary.UploadFile(testFile); + + // var downloadedFile = secondary.DownloadContent(contentId); + + // testFile.AssertIsEqual(downloadedFile); + //} } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index fc5152f..136951d 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -13,7 +13,7 @@ - +