Add LegalDocuments class code with unit tests
MODIFY
WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs
WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using WalletWasabi.Backend.Models.Responses;
using WalletWasabi.Blockchain.BlockFilters;
using WalletWasabi.CoinJoin.Client.Clients;
+using WalletWasabi.Legal;
using WalletWasabi.Models;
using WalletWasabi.Services;
using WalletWasabi.Tests.XunitConfiguration;

ADD
WalletWasabi.Tests/UnitTests/LegalTests.cs
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.Helpers;
+using WalletWasabi.Legal;
+using Xunit;
+
+namespace WalletWasabi.Tests.UnitTests
+{
+ public class LegalTests
+ {
+ [Fact]
+ public void CanCreateLegalDocuments()
+ {
+ var version = new Version(1, 1);
+ var legal = new LegalDocuments($"{version}.txt");
+ Assert.Equal("1.1.txt", legal.FilePath);
+ Assert.Equal(version, legal.Version);
+ }
+
+ [Fact]
+ public void LegalDocumentsCreationFixesPath()
+ {
+ var legal = new LegalDocuments($" 1.1.txt ");
+ Assert.Equal("1.1.txt", legal.FilePath);
+ }
+
+ [Fact]
+ public void LegalDocumentsCreationThrows()
+ {
+ Assert.Throws<ArgumentException>(() => new LegalDocuments("foo"));
+ Assert.Throws<ArgumentException>(() => new LegalDocuments("foo.txt"));
+ Assert.Throws<ArgumentException>(() => new LegalDocuments("0.txt"));
+ Assert.Throws<ArgumentNullException>(() => new LegalDocuments(null));
+ Assert.Throws<ArgumentException>(() => new LegalDocuments(""));
+ Assert.Throws<ArgumentException>(() => new LegalDocuments(" "));
+ }
+
+ [Fact]
+ public void CantLoadNotAgreed()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ var res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ Assert.True(Directory.Exists(dir));
+ Assert.Empty(Directory.GetFiles(dir));
+ var legalDir = Assert.Single(Directory.GetDirectories(dir));
+ Assert.Empty(Directory.GetFiles(legalDir));
+ Assert.Empty(Directory.GetDirectories(legalDir));
+ }
+
+ [Fact]
+ public async Task LeavesTrashAloneAsync()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ var res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ var legalDir = Assert.Single(Directory.GetDirectories(dir));
+
+ // Leaves one trash alone.
+ var trash1 = File.Create(Path.Combine(legalDir, "foo"));
+ await trash1.DisposeAsync();
+ res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ Assert.Single(Directory.GetFiles(legalDir));
+ Assert.Empty(Directory.GetDirectories(legalDir));
+
+ // Leaves 3 trash alone.
+ var trash2 = File.Create(Path.Combine(legalDir, "foo.txt"));
+ var trash3 = File.Create(Path.Combine(legalDir, "foo2"));
+ await trash2.DisposeAsync();
+ await trash3.DisposeAsync();
+ res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ Assert.Equal(3, Directory.GetFiles(legalDir).Length);
+ Assert.Empty(Directory.GetDirectories(legalDir));
+ }
+
+ [Fact]
+ public async Task ResolvesConflictsAsync()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ var res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ var legalDir = Assert.Single(Directory.GetDirectories(dir));
+
+ // Deletes them if multiple legal docs found.
+ var candidate1 = File.Create(Path.Combine(legalDir, "1.1.txt"));
+ var candidate2 = File.Create(Path.Combine(legalDir, "1.2.txt"));
+ await candidate1.DisposeAsync();
+ await candidate2.DisposeAsync();
+ res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ Assert.Empty(Directory.GetFiles(legalDir));
+ Assert.Empty(Directory.GetDirectories(legalDir));
+
+ // Only the candidates are deleted.
+ var trash = File.Create(Path.Combine(legalDir, "1.txt"));
+ candidate1 = File.Create(Path.Combine(legalDir, "1.1.txt"));
+ candidate2 = File.Create(Path.Combine(legalDir, "1.2.txt"));
+ await trash.DisposeAsync();
+ await candidate1.DisposeAsync();
+ await candidate2.DisposeAsync();
+ res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ Assert.Single(Directory.GetFiles(legalDir));
+ Assert.Empty(Directory.GetDirectories(legalDir));
+ }
+
+ [Fact]
+ public async Task CanLoadLegalDocsAsync()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ var res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.Null(res);
+ var legalDir = Assert.Single(Directory.GetDirectories(dir));
+
+ var version = new Version(1, 1);
+ // Deletes them if multiple legal docs found.
+ var candidate = File.Create(Path.Combine(legalDir, $"{version}.txt"));
+ await candidate.DisposeAsync();
+ res = LegalDocuments.TryLoadAgreed(dir);
+ Assert.NotNull(res);
+ Assert.Single(Directory.GetFiles(legalDir));
+ Assert.Empty(Directory.GetDirectories(legalDir));
+ Assert.Equal(version, res.Version);
+ Assert.Equal(Path.Combine(legalDir, $"{version}.txt"), res.FilePath);
+ }
+
+ [Fact]
+ public async Task LegalDocFileNameMustBeProperlyFormattedAsync()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ await Assert.ThrowsAsync<ArgumentException>(async () => await LegalDocuments.ToFileAsync(Path.Combine("foo"), "bar"));
+ await Assert.ThrowsAsync<ArgumentException>(async () => await LegalDocuments.ToFileAsync(Path.Combine("foo.txt"), "bar"));
+ await Assert.ThrowsAsync<ArgumentException>(async () => await LegalDocuments.ToFileAsync(Path.Combine("1.txt"), "bar"));
+ }
+
+ [Fact]
+ public async Task CanSerializeFileAsync()
+ {
+ var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ string filePath = Path.Combine(dir, "1.1.txt");
+ var legal = new LegalDocuments(filePath);
+
+ Assert.False(File.Exists(filePath));
+ await legal.ToFileAsync("bar");
+ Assert.True(File.Exists(filePath));
+
+ string filePath2 = Path.Combine(dir, "1.2.txt");
+ var legal2 = new LegalDocuments(filePath2);
+
+ Assert.True(File.Exists(filePath));
+ Assert.False(File.Exists(filePath2));
+ await legal2.ToFileAsync("bar");
+ Assert.False(File.Exists(filePath));
+ Assert.True(File.Exists(filePath2));
+ }
+ }
+}

MODIFY
WalletWasabi/Legal/LegalDocuments.cs
WalletWasabi/Legal/LegalDocuments.cs
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+using WalletWasabi.WebClients.Wasabi;
namespace WalletWasabi.Legal
{
@@ -9,5 +17,65 @@ namespace WalletWasabi.Legal
public const string LegalFolderName = "Legal";
public const string AssetsFoldername = "Assets";
public static readonly string EmbeddedFilePath = Path.Combine(EnvironmentHelpers.GetFullBaseDirectory(), LegalFolderName, AssetsFoldername, EmbeddedFileName);
+
+ public LegalDocuments(string filePath)
+ {
+ FilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(filePath), filePath, trim: true);
+ var verString = Path.GetFileNameWithoutExtension(FilePath);
+ var version = Version.Parse(verString);
+ Version = Guard.NotNull(nameof(version), version);
+ }
+
+ public string FilePath { get; }
+ public Version Version { get; }
+
+ public static LegalDocuments TryLoadAgreed(string dataDir)
+ {
+ var legalFolderPath = Path.Combine(dataDir, LegalFolderName);
+ IoHelpers.EnsureDirectoryExists(legalFolderPath);
+ var legalDocCandidates = FindCandidates(legalFolderPath);
+ var legalDocCandidateCount = legalDocCandidates.Count();
+
+ if (legalDocCandidateCount == 1)
+ {
+ var filePath = legalDocCandidates.Single();
+ return new LegalDocuments(filePath);
+ }
+ else if (legalDocCandidateCount > 1)
+ {
+ RemoveCandidates(legalFolderPath);
+ }
+
+ return null;
+ }
+
+ private static IEnumerable<string> FindCandidates(string legalFolderPath)
+ {
+ return Directory
+ .EnumerateFiles(legalFolderPath, "*.txt", SearchOption.TopDirectoryOnly)
+ .Where(x => Version.TryParse(Path.GetFileNameWithoutExtension(x), out _));
+ }
+
+ public async static Task ToFileAsync(string filePath, string content)
+ {
+ // Throw exception if filepath is incorrectly formatted.
+ Version.Parse(Path.GetFileNameWithoutExtension(filePath));
+
+ var legalFolderPath = Path.GetDirectoryName(filePath);
+ RemoveCandidates(legalFolderPath);
+ await File.WriteAllTextAsync(filePath, content).ConfigureAwait(false);
+ }
+
+ private static void RemoveCandidates(string legalFolderPath)
+ {
+ IoHelpers.EnsureDirectoryExists(legalFolderPath);
+ foreach (var candidate in FindCandidates(legalFolderPath))
+ {
+ File.Delete(candidate);
+ Logger.LogInfo($"Removed legal doc candidate: {candidate}.");
+ }
+ }
+
+ public async Task ToFileAsync(string content) => await ToFileAsync(FilePath, content).ConfigureAwait(false);
}
}

Add LegalDocuments class code with unit tests