Complete: Accounts, persistence, unit tests

This commit is contained in:
Robert McRackan 2020-08-16 23:04:23 -04:00
parent 643ae09b2b
commit 1ad2135a3f
2 changed files with 268 additions and 38 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApi;
using AudibleApi.Authorization;
using Dinah.Core;
@ -28,7 +29,12 @@ namespace InternalUtilities
{
public event EventHandler Updated;
private void update(object sender = null, EventArgs e = null)
=> Updated?.Invoke(this, new EventArgs());
{
foreach (var account in AccountsSettings)
validate(account);
update_no_validate();
}
private void update_no_validate() => Updated?.Invoke(this, new EventArgs());
public Accounts() { }
@ -45,15 +51,13 @@ namespace InternalUtilities
// 'set' is only used by json deser
set
{
_accountsSettings_backing = value;
if (_accountsSettings_backing is null)
if (value is null)
return;
foreach (var acct in _accountsSettings_backing)
acct.Updated += update;
foreach (var account in value)
_add(account);
update();
update_no_validate();
}
}
[JsonIgnore]
@ -66,29 +70,78 @@ namespace InternalUtilities
public string ToJson(Formatting formatting = Formatting.Indented)
=> JsonConvert.SerializeObject(this, formatting, Identity.GetJsonSerializerSettings());
public void UNITTEST_Seed(Account account)
public void Add(Account account)
{
_accountsSettings_backing.Add(account);
update();
_add(account);
update_no_validate();
}
// replace UNITTEST_Seed
public void _add(Account account)
{
validate(account);
// when creating Account object (get, update, insert), subscribe to it's update. including all new ones on initial load
// removing: unsubscribe
_accountsSettings_backing.Add(account);
account.Updated += update;
}
// IEnumerable<Account> GetAllAccounts
// more common naming convention alias for internal collection
public IReadOnlyList<Account> GetAll() => AccountsSettings;
// void UpsertAccount (id, locale)
// if not exists
// create account w/null identity
// save in file
// return Account?
// return bool/enum of whether is newly created?
public Account GetAccount(string accountId, string locale)
{
if (locale is null)
return null;
// how to persist edits to [Account] obj?
// account name, decryptkey, id tokens, ...
// persistence happens in [Accounts], not [Account]. an [Account] accidentally created directly shouldn't mess up expected workflow ???
return AccountsSettings.SingleOrDefault(a => a.AccountId == accountId && a.IdentityTokens.Locale.Name == locale);
}
public Account Upsert(string accountId, string locale)
{
var acct = GetAccount(accountId, locale);
if (acct != null)
return acct;
var l = Localization.Locales.Single(l => l.Name == locale);
var id = new Identity(l);
var account = new Account(accountId) { IdentityTokens = id };
Add(account);
return account;
}
public bool Delete(Account account)
{
if (!_accountsSettings_backing.Contains(account))
return false;
account.Updated -= update;
return _accountsSettings_backing.Remove(account);
}
public bool Delete(string accountId, string locale)
{
var acct = GetAccount(accountId, locale);
if (acct is null)
return false;
return Delete(acct);
}
private void validate(Account account)
{
ArgumentValidator.EnsureNotNull(account, nameof(account));
var accountId = account.AccountId;
var locale = account?.IdentityTokens?.Locale?.Name;
var acct = GetAccount(accountId, locale);
if (acct is null || account is null)
return;
if (acct != account)
throw new InvalidOperationException("Cannot add an account with the same account Id and Locale");
}
}
public class Account : Updatable
{

View File

@ -10,6 +10,7 @@ using AudibleApi;
using AudibleApi.Authorization;
using Dinah.Core;
using FluentAssertions;
using FluentAssertions.Common;
using InternalUtilities;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -133,7 +134,7 @@ namespace AccountsTests
}
}
public class AccountsPersisterTestBase
public class AccountsTestBase
{
protected string TestFile;
@ -153,7 +154,7 @@ namespace AccountsTests
}
[TestClass]
public class ctor : AccountsPersisterTestBase
public class ctor : AccountsTestBase
{
[TestMethod]
public void create_file()
@ -190,8 +191,8 @@ namespace AccountsTests
public void save_multiple_children()
{
var accounts = new Accounts();
accounts.UNITTEST_Seed(new Account("a0") { AccountName = "n0" });
accounts.UNITTEST_Seed(new Account("a1") { AccountName = "n1" });
accounts.Add(new Account("a0") { AccountName = "n0" });
accounts.Add(new Account("a1") { AccountName = "n1" });
// dispose to cease auto-updates
using (var p = new AccountsPersister(accounts, TestFile)) { }
@ -209,7 +210,7 @@ namespace AccountsTests
var idJson = JsonConvert.SerializeObject(id, Identity.GetJsonSerializerSettings());
var accounts = new Accounts();
accounts.UNITTEST_Seed(new Account("a0") { AccountName = "n0", IdentityTokens = id });
accounts.Add(new Account("a0") { AccountName = "n0", IdentityTokens = id });
// dispose to cease auto-updates
using (var p = new AccountsPersister(accounts, TestFile)) { }
@ -222,7 +223,7 @@ namespace AccountsTests
}
[TestClass]
public class save : AccountsPersisterTestBase
public class save : AccountsTestBase
{
// add/save account after file creation
[TestMethod]
@ -238,7 +239,7 @@ namespace AccountsTests
var idIn = new Identity(localeIn);
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
p.Accounts.UNITTEST_Seed(acctIn);
p.Accounts.Add(acctIn);
}
// re-load file. ensure account still exists
@ -266,7 +267,7 @@ namespace AccountsTests
var idIn = new Identity(localeIn);
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
p.Accounts.UNITTEST_Seed(acctIn);
p.Accounts.Add(acctIn);
}
// re-load file. ensure account still exists
@ -286,7 +287,7 @@ namespace AccountsTests
var idIn = new Identity(localeIn);
var acctIn = new Account("a1") { AccountName = "n1", IdentityTokens = idIn };
p.Accounts.UNITTEST_Seed(acctIn);
p.Accounts.Add(acctIn);
}
// re-load file. ensure both accounts still exist
@ -304,6 +305,32 @@ namespace AccountsTests
}
}
[TestMethod]
public void update_Account_field_just_added()
{
// create initial file
using (var p = new AccountsPersister(new Accounts(), TestFile)) { }
// load file. create 2 accounts
using (var p = new AccountsPersister(TestFile))
{
var locale1 = Localization.Locales.Single(l => l.Name == "us");
var id1 = new Identity(locale1);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.Accounts.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 AccountsPersister(TestFile))
{
var acct0 = p.Accounts.AccountsSettings[0];
acct0.AccountName.Should().Be("new");
}
}
// update Account property. must be non-destructive to all other data
[TestMethod]
public void update_Account_field()
@ -317,13 +344,13 @@ namespace AccountsTests
var locale1 = Localization.Locales.Single(l => l.Name == "us");
var id1 = new Identity(locale1);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.Accounts.UNITTEST_Seed(acct1);
p.Accounts.Add(acct1);
var locale2 = Localization.Locales.Single(l => l.Name == "uk");
var id2 = new Identity(locale2);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.Accounts.UNITTEST_Seed(acct2);
p.Accounts.Add(acct2);
}
// update AccountName on existing file
@ -363,13 +390,13 @@ namespace AccountsTests
var locale1 = Localization.Locales.Single(l => l.Name == "us");
var id1 = new Identity(locale1);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.Accounts.UNITTEST_Seed(acct1);
p.Accounts.Add(acct1);
var locale2 = Localization.Locales.Single(l => l.Name == "uk");
var id2 = new Identity(locale2);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.Accounts.UNITTEST_Seed(acct2);
p.Accounts.Add(acct2);
}
// update identity on existing file
@ -413,13 +440,13 @@ namespace AccountsTests
var locale1 = Localization.Locales.Single(l => l.Name == "us");
var id1 = new Identity(locale1);
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
p.Accounts.UNITTEST_Seed(acct1);
p.Accounts.Add(acct1);
var locale2 = Localization.Locales.Single(l => l.Name == "uk");
var id2 = new Identity(locale2);
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
p.Accounts.UNITTEST_Seed(acct2);
p.Accounts.Add(acct2);
}
// update identity on existing file
@ -448,4 +475,154 @@ namespace AccountsTests
}
}
}
[TestClass]
public class retrieve : AccountsTestBase
{
[TestMethod]
public void get_where()
{
var us = Localization.Locales.Single(l => l.Name == "us");
var idUs = new Identity(us);
var acct1 = new Account("cng") { IdentityTokens = idUs, AccountName = "foo" };
var uk = Localization.Locales.Single(l => l.Name == "uk");
var idUk = new Identity(uk);
var acct2 = new Account("cng") { IdentityTokens = idUk, AccountName = "bar" };
var accounts = new Accounts();
accounts.Add(acct1);
accounts.Add(acct2);
accounts.GetAccount("cng", "uk").AccountName.Should().Be("bar");
}
}
[TestClass]
public class upsert : AccountsTestBase
{
[TestMethod]
public void upsert_new()
{
var accounts = new Accounts();
accounts.AccountsSettings.Count.Should().Be(0);
accounts.Upsert("cng", "us");
accounts.AccountsSettings.Count.Should().Be(1);
accounts.GetAccount("cng", "us").AccountId.Should().Be("cng");
}
[TestMethod]
public void upsert_exists()
{
var accounts = new Accounts();
var orig = accounts.Upsert("cng", "us");
orig.AccountName = "foo";
var exists = accounts.Upsert("cng", "us");
exists.AccountName.Should().Be("foo");
orig.Should().IsSameOrEqualTo(exists);
}
}
[TestClass]
public class delete : AccountsTestBase
{
[TestMethod]
public void delete_account()
{
var accounts = new Accounts();
var acct = accounts.Upsert("cng", "us");
accounts.AccountsSettings.Count.Should().Be(1);
var removed = accounts.Delete(acct);
removed.Should().BeTrue();
accounts.AccountsSettings.Count.Should().Be(0);
}
[TestMethod]
public void delete_where()
{
var accounts = new Accounts();
var acct = accounts.Upsert("cng", "us");
accounts.AccountsSettings.Count.Should().Be(1);
accounts.Delete("baz", "baz").Should().BeFalse();
accounts.AccountsSettings.Count.Should().Be(1);
accounts.Delete("cng", "us").Should().BeTrue();
accounts.AccountsSettings.Count.Should().Be(0);
}
[TestMethod]
public void deleted_account_should_not_persist_file()
{
Account acct;
using (var p = new AccountsPersister(new Accounts(), TestFile))
{
acct = p.Accounts.Upsert("foo", "us");
p.Accounts.AccountsSettings.Count.Should().Be(1);
acct.AccountName = "old";
}
using (var p = new AccountsPersister(new Accounts(), TestFile))
{
p.Accounts.Delete(acct);
p.Accounts.AccountsSettings.Count.Should().Be(0);
}
using (var p = new AccountsPersister(new Accounts(), TestFile))
{
File.ReadAllText(TestFile).Should().Be("{\r\n \"AccountsSettings\": []\r\n}".Trim());
acct.AccountName = "new";
File.ReadAllText(TestFile).Should().Be("{\r\n \"AccountsSettings\": []\r\n}".Trim());
}
}
}
// account.Id + Locale.Name -- must be unique
[TestClass]
public class validate : AccountsTestBase
{
[TestMethod]
public void violate_validation()
{
var accounts = new Accounts();
var localeIn = Localization.Locales.Single(l => l.Name == "us");
var idIn = new Identity(localeIn);
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
accounts.Add(a1);
var a2 = new Account("a") { AccountName = "two", IdentityTokens = idIn };
// violation: validate()
Assert.ThrowsException<InvalidOperationException>(() => accounts.Add(a2));
}
[TestMethod]
public void identity_violate_validation()
{
var accounts = new Accounts();
var localeIn = Localization.Locales.Single(l => l.Name == "us");
var idIn = new Identity(localeIn);
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
accounts.Add(a1);
var a2 = new Account("a") { AccountName = "two" };
accounts.Add(a2);
// violation: GetAccount.SingleOrDefault
Assert.ThrowsException<InvalidOperationException>(() => a2.IdentityTokens = idIn);
}
}
}