2022-12-16 09:27:44 -05:00

594 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AudibleApi;
using AudibleApi.Authorization;
using AudibleUtilities;
using Dinah.Core;
using FluentAssertions;
using FluentAssertions.Common;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AccountsTests
{
public class AccountsTestBase
{
protected string EMPTY_FILE { get; } = "{\r\n \"Accounts\": []\r\n}".Replace("\r\n", Environment.NewLine);
protected string TestFile;
protected Locale usLocale => Localization.Get("us");
protected Locale ukLocale => Localization.Get("uk");
protected void WriteToTestFile(string contents)
=> File.WriteAllText(TestFile, contents);
[TestInitialize]
public void TestInit()
=> TestFile = Guid.NewGuid() + ".txt";
[TestCleanup]
public void TestCleanup()
{
if (File.Exists(TestFile))
File.Delete(TestFile);
}
}
[TestClass]
public class FromJson : AccountsTestBase
{
[TestMethod]
public void _0_accounts()
{
var accountsSettings = AccountsSettings.FromJson(EMPTY_FILE);
accountsSettings.Accounts.Count.Should().Be(0);
}
[TestMethod]
public void _1_account_new()
{
var json = @"
{
""Accounts"": [
{
""AccountId"": ""cng"",
""AccountName"": ""my main login"",
""DecryptKey"": ""asdfasdf"",
""IdentityTokens"": null
}
]
}
".Trim();
var accountsSettings = AccountsSettings.FromJson(json);
accountsSettings.Accounts.Count.Should().Be(1);
accountsSettings.Accounts[0].AccountId.Should().Be("cng");
accountsSettings.Accounts[0].IdentityTokens.Should().BeNull();
}
}
[TestClass]
public class ctor : AccountsTestBase
{
[TestMethod]
public void create_file()
{
File.Exists(TestFile).Should().BeFalse();
var accountsSettings = new AccountsSettings();
_ = new AccountsSettingsPersister(accountsSettings, TestFile);
File.Exists(TestFile).Should().BeTrue();
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
}
[TestMethod]
public void overwrite_existing_file()
{
File.Exists(TestFile).Should().BeFalse();
WriteToTestFile("foo");
File.Exists(TestFile).Should().BeTrue();
var accountsSettings = new AccountsSettings();
_ = new AccountsSettingsPersister(accountsSettings, TestFile);
File.Exists(TestFile).Should().BeTrue();
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
}
[TestMethod]
public void save_multiple_children()
{
var accountsSettings = new AccountsSettings();
accountsSettings.Add(new Account("a0") { AccountName = "n0" });
accountsSettings.Add(new Account("a1") { AccountName = "n1" });
// dispose to cease auto-updates
using (var p = new AccountsSettingsPersister(accountsSettings, TestFile)) { }
var persister = new AccountsSettingsPersister(TestFile);
persister.AccountsSettings.Accounts.Count.Should().Be(2);
persister.AccountsSettings.Accounts[1].AccountName.Should().Be("n1");
}
[TestMethod]
public void save_with_identity()
{
var id = new Identity(usLocale);
var idJson = JsonConvert.SerializeObject(id, Identity.GetJsonSerializerSettings());
var accountsSettings = new AccountsSettings();
accountsSettings.Add(new Account("a0") { AccountName = "n0", IdentityTokens = id });
// dispose to cease auto-updates
using (var p = new AccountsSettingsPersister(accountsSettings, TestFile)) { }
var persister = new AccountsSettingsPersister(TestFile);
var acct = persister.AccountsSettings.Accounts[0];
acct.AccountName.Should().Be("n0");
acct.Locale.CountryCode.Should().Be("us");
}
}
[TestClass]
public class save : AccountsTestBase
{
// add/save account after file creation
[TestMethod]
public void save_1_account()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create account
using (var p = new AccountsSettingsPersister(TestFile))
{
var idIn = new Identity(usLocale);
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
p.AccountsSettings.Add(acctIn);
}
// re-load file. ensure account still exists
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(1);
var acct0 = p.AccountsSettings.Accounts[0];
acct0.AccountName.Should().Be("n0");
acct0.Locale.CountryCode.Should().Be("us");
}
}
// add/save mult accounts after file creation
// separately create 2 accounts. ensure both still exist in the end
[TestMethod]
public void save_2_accounts()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create account 0
using (var p = new AccountsSettingsPersister(TestFile))
{
var idIn = new Identity(usLocale);
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
p.AccountsSettings.Add(acctIn);
}
// re-load file. ensure account still exists
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(1);
var acct0 = p.AccountsSettings.Accounts[0];
acct0.AccountName.Should().Be("n0");
acct0.Locale.CountryCode.Should().Be("us");
}
// load file. create account 1
using (var p = new AccountsSettingsPersister(TestFile))
{
var idIn = new Identity(ukLocale);
var acctIn = new Account("a1") { AccountName = "n1", IdentityTokens = idIn };
p.AccountsSettings.Add(acctIn);
}
// re-load file. ensure both accounts still exist
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(2);
var acct0 = p.AccountsSettings.Accounts[0];
acct0.AccountName.Should().Be("n0");
acct0.Locale.CountryCode.Should().Be("us");
var acct1 = p.AccountsSettings.Accounts[1];
acct1.AccountName.Should().Be("n1");
acct1.Locale.CountryCode.Should().Be("uk");
}
}
[TestMethod]
public void update_Account_field_just_added()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create 2 accounts
using (var p = new AccountsSettingsPersister(TestFile))
{
var id1 = new Identity(usLocale);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.AccountsSettings.Add(acct1);
// update just-added item. note: this is different than the subscription which happens on initial collection load. ensure this works also
acct1.AccountName = "new";
}
// verify save property
using (var p = new AccountsSettingsPersister(TestFile))
{
var acct0 = p.AccountsSettings.Accounts[0];
acct0.AccountName.Should().Be("new");
}
}
// update Account property. must be non-destructive to all other data
[TestMethod]
public void update_Account_field()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create 2 accounts
using (var p = new AccountsSettingsPersister(TestFile))
{
var id1 = new Identity(usLocale);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.AccountsSettings.Add(acct1);
var id2 = new Identity(ukLocale);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.AccountsSettings.Add(acct2);
}
// update AccountName on existing file
using (var p = new AccountsSettingsPersister(TestFile))
{
var acct0 = p.AccountsSettings.Accounts[0];
acct0.AccountName = "new";
}
// re-load file. ensure both accounts still exist
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(2);
var acct0 = p.AccountsSettings.Accounts[0];
// new
acct0.AccountName.Should().Be("new");
// still here
acct0.Locale.CountryCode.Should().Be("us");
var acct1 = p.AccountsSettings.Accounts[1];
acct1.AccountName.Should().Be("n1");
acct1.Locale.CountryCode.Should().Be("uk");
}
}
// update identity. must be non-destructive to all other data
[TestMethod]
public void replace_identity()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create 2 accounts
using (var p = new AccountsSettingsPersister(TestFile))
{
var id1 = new Identity(usLocale);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.AccountsSettings.Add(acct1);
var id2 = new Identity(ukLocale);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.AccountsSettings.Add(acct2);
}
// update identity on existing file
using (var p = new AccountsSettingsPersister(TestFile))
{
var id = new Identity(ukLocale);
var acct0 = p.AccountsSettings.Accounts[0];
acct0.IdentityTokens = id;
}
// re-load file. ensure both accounts still exist
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(2);
var acct0 = p.AccountsSettings.Accounts[0];
// new
acct0.Locale.CountryCode.Should().Be("uk");
// still here
acct0.AccountName.Should().Be("n0");
var acct1 = p.AccountsSettings.Accounts[1];
acct1.AccountName.Should().Be("n1");
acct1.Locale.CountryCode.Should().Be("uk");
}
}
// multi-level subscribe => update
// edit field of existing identity. must be non-destructive to all other data
[TestMethod]
public void update_identity_field()
{
// create initial file
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
// load file. create 2 accounts
using (var p = new AccountsSettingsPersister(TestFile))
{
var id1 = new Identity(usLocale);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.AccountsSettings.Add(acct1);
var id2 = new Identity(ukLocale);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.AccountsSettings.Add(acct2);
}
// update identity on existing file
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts[0]
.IdentityTokens
.Update(new AccessToken("Atna|_NEW_", DateTime.Now.AddDays(1)));
}
// re-load file. ensure both accounts still exist
using (var p = new AccountsSettingsPersister(TestFile))
{
p.AccountsSettings.Accounts.Count.Should().Be(2);
var acct0 = p.AccountsSettings.Accounts[0];
// new
acct0.IdentityTokens.ExistingAccessToken.TokenValue.Should().Be("Atna|_NEW_");
// still here
acct0.AccountName.Should().Be("n0");
acct0.Locale.CountryCode.Should().Be("us");
var acct1 = p.AccountsSettings.Accounts[1];
acct1.AccountName.Should().Be("n1");
acct1.Locale.CountryCode.Should().Be("uk");
}
}
}
[TestClass]
public class retrieve : AccountsTestBase
{
[TestMethod]
public void get_where()
{
var idUs = new Identity(usLocale);
var acct1 = new Account("cng") { IdentityTokens = idUs, AccountName = "foo" };
var idUk = new Identity(ukLocale);
var acct2 = new Account("cng") { IdentityTokens = idUk, AccountName = "bar" };
var accountsSettings = new AccountsSettings();
accountsSettings.Add(acct1);
accountsSettings.Add(acct2);
accountsSettings.GetAccount("cng", "uk").AccountName.Should().Be("bar");
}
}
[TestClass]
public class upsert : AccountsTestBase
{
[TestMethod]
public void upsert_new()
{
var accountsSettings = new AccountsSettings();
accountsSettings.Accounts.Count.Should().Be(0);
accountsSettings.Upsert("cng", "us");
accountsSettings.Accounts.Count.Should().Be(1);
accountsSettings.GetAccount("cng", "us").AccountId.Should().Be("cng");
}
[TestMethod]
public void upsert_exists()
{
var accountsSettings = new AccountsSettings();
var orig = accountsSettings.Upsert("cng", "us");
orig.AccountName = "foo";
var exists = accountsSettings.Upsert("cng", "us");
exists.AccountName.Should().Be("foo");
orig.Should().BeSameAs(exists);
}
}
[TestClass]
public class delete : AccountsTestBase
{
[TestMethod]
public void delete_account()
{
var accountsSettings = new AccountsSettings();
var acct = accountsSettings.Upsert("cng", "us");
accountsSettings.Accounts.Count.Should().Be(1);
var removed = accountsSettings.Delete(acct);
removed.Should().BeTrue();
accountsSettings.Accounts.Count.Should().Be(0);
}
[TestMethod]
public void delete_where()
{
var accountsSettings = new AccountsSettings();
_ = accountsSettings.Upsert("cng", "us");
accountsSettings.Accounts.Count.Should().Be(1);
accountsSettings.Delete("baz", "baz").Should().BeFalse();
accountsSettings.Accounts.Count.Should().Be(1);
accountsSettings.Delete("cng", "us").Should().BeTrue();
accountsSettings.Accounts.Count.Should().Be(0);
}
[TestMethod]
public void delete_updates()
{
var i = 0;
void update(object sender, EventArgs e) => i++;
var accountsSettings = new AccountsSettings();
accountsSettings.Updated += update;
accountsSettings.Accounts.Count.Should().Be(0);
i.Should().Be(0);
_ = accountsSettings.Upsert("cng", "us");
accountsSettings.Accounts.Count.Should().Be(1);
i.Should().Be(1);
accountsSettings.Delete("baz", "baz").Should().BeFalse();
accountsSettings.Accounts.Count.Should().Be(1);
i.Should().Be(1);
accountsSettings.Delete("cng", "us").Should().BeTrue();
accountsSettings.Accounts.Count.Should().Be(0);
i.Should().Be(2); // <== this is the one being tested
}
[TestMethod]
public void deleted_account_should_not_persist_file()
{
Account acct;
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
{
acct = p.AccountsSettings.Upsert("foo", "us");
p.AccountsSettings.Accounts.Count.Should().Be(1);
acct.AccountName = "old";
}
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
{
p.AccountsSettings.Delete(acct);
p.AccountsSettings.Accounts.Count.Should().Be(0);
}
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
{
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
acct.AccountName = "new";
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
}
}
}
// account.Id + Locale.Name -- must be unique
[TestClass]
public class validate : AccountsTestBase
{
[TestMethod]
public void violate_validation()
{
var accountsSettings = new AccountsSettings();
var idIn = new Identity(usLocale);
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
accountsSettings.Add(a1);
var a2 = new Account("a") { AccountName = "two", IdentityTokens = idIn };
// violation: validate()
Assert.ThrowsException<InvalidOperationException>(() => accountsSettings.Add(a2));
}
[TestMethod]
public void identity_violate_validation()
{
var accountsSettings = new AccountsSettings();
var idIn = new Identity(usLocale);
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
accountsSettings.Add(a1);
var a2 = new Account("a") { AccountName = "two" };
accountsSettings.Add(a2);
// violation: GetAccount.SingleOrDefault
Assert.ThrowsException<InvalidOperationException>(() => a2.IdentityTokens = idIn);
}
}
[TestClass]
public class transactions : AccountsTestBase
{
[TestMethod]
public void atomic_update_at_end()
{
var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile);
p.BeginTransation();
// upserted account will not persist until CommitTransation
var acct = p.AccountsSettings.Upsert("cng", "us");
acct.AccountName = "foo";
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
p.IsInTransaction.Should().BeTrue();
p.CommitTransation();
p.IsInTransaction.Should().BeFalse();
}
[TestMethod]
public void abandoned_transaction()
{
var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile);
try
{
p.BeginTransation();
var acct = p.AccountsSettings.Upsert("cng", "us");
acct.AccountName = "foo";
throw new Exception();
}
catch { }
finally
{
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
p.IsInTransaction.Should().BeTrue();
}
}
}
}