use ed25519_dalek::{Signer, SigningKey};
use kcode_k1_accounts::K1Accounts;
use kcode_k1_invites::{InviteStatus, K1Invites};
use kcode_k1_peering::K1Peering;
use kcode_k1_persons::K1Persons;
use kcode_k1_terms::{self, REVISION};
use kcode_k1_txn_ordering::K1TxnOrdering;
use kcode_k1_users::{K1Users, NewUser};
use std::path::Path;
use std::sync::{Arc, Barrier};
use tempfile::TempDir;
struct Stack {
ordering: Arc<K1TxnOrdering>,
invites: Arc<K1Invites>,
accounts: Arc<K1Accounts>,
persons: Arc<K1Persons>,
users: Arc<K1Users>,
}
fn open(root: &Path) -> Stack {
let ordering = Arc::new(K1TxnOrdering::open(&root.join("ordering")).unwrap());
let peering = Arc::new(K1Peering::open(&root.join("peering"), ordering.clone()).unwrap());
let invites = Arc::new(
K1Invites::open(&root.join("invites"), ordering.clone(), peering.clone()).unwrap(),
);
let persons = Arc::new(
K1Persons::open(&root.join("persons"), ordering.clone(), peering.clone()).unwrap(),
);
let accounts = Arc::new(K1Accounts::open(invites.clone()).unwrap());
let users = Arc::new(K1Users::new(accounts.clone(), persons.clone()));
Stack {
ordering,
invites,
accounts,
persons,
users,
}
}
fn credentials(seed: u8) -> ([u8; 32], [u8; 64]) {
let key = SigningKey::from_bytes(&[seed; 32]);
(
key.verifying_key().to_bytes(),
key.sign(kcode_k1_terms::text().as_bytes()).to_bytes(),
)
}
fn new_user(username: &str, full_name: &str, seed: u8) -> NewUser {
let (public_key, signature) = credentials(seed);
NewUser::new(
username,
full_name,
public_key,
REVISION,
kcode_k1_terms::sha256(),
signature,
)
.unwrap()
}
#[test]
fn invalid_input_has_no_effect() {
let root = TempDir::new().unwrap();
let stack = open(root.path());
let (_, code) = stack.invites.create().unwrap();
let before = stack.ordering.tip();
let (public_key, signature) = credentials(1);
assert!(
NewUser::new(
"Alice",
"Alice",
public_key,
REVISION,
kcode_k1_terms::sha256(),
signature,
)
.is_err()
);
assert!(
NewUser::new(
"alice",
" \t",
public_key,
REVISION,
kcode_k1_terms::sha256(),
signature,
)
.is_err()
);
assert_eq!(stack.ordering.tip(), before);
assert_eq!(stack.invites.status(&code).unwrap(), InviteStatus::Unused);
assert!(
stack
.users
.get(kcode_k1_invites::UserId::from_tx_id(
kcode_k1_txn_ordering::TxId::from_bytes([9; 12])
))
.unwrap()
.is_none()
);
}
#[test]
fn register_read_and_restart() {
let root = TempDir::new().unwrap();
let (user_id, anchor, public_key, signature) = {
let stack = open(root.path());
let (_, code) = stack.invites.create().unwrap();
let (public_key, signature) = credentials(3);
let user = stack
.users
.register(&code, new_user("alice", "Alice Example", 3))
.unwrap();
let user_id = user.user_id();
let anchor = user.person_anchor_id();
assert_eq!(user.username(), "alice");
assert_eq!(user.person_id(), anchor);
assert_eq!(user.full_name(), "Alice Example");
assert_eq!(user.public_key(), public_key);
assert_eq!(user.tos_revision(), REVISION);
assert_eq!(user.tos_digest(), kcode_k1_terms::sha256());
assert_eq!(user.acceptance_signature(), signature);
assert_eq!(
stack.users.get(user_id).unwrap().unwrap().person_id(),
anchor
);
assert_eq!(
stack
.users
.find_by_username("alice")
.unwrap()
.unwrap()
.user_id(),
user_id
);
assert_eq!(
stack.accounts.get(user_id).unwrap().unwrap().person_id(),
anchor
);
(user_id, anchor, public_key, signature)
};
let stack = open(root.path());
let user = stack.users.get(user_id).unwrap().unwrap();
assert_eq!(user.person_anchor_id(), anchor);
assert_eq!(user.person_id(), anchor);
assert_eq!(user.full_name(), "Alice Example");
assert_eq!(user.public_key(), public_key);
assert_eq!(user.acceptance_signature(), signature);
}
#[test]
fn retry_and_conflict_precede_effects() {
let root = TempDir::new().unwrap();
let stack = open(root.path());
let (_, code) = stack.invites.create().unwrap();
let first = stack
.users
.register(&code, new_user("alice", "Original", 5))
.unwrap();
let retry_tip = stack.ordering.tip();
let retry = stack
.users
.register(&code, new_user("alice", "Ignored retry name", 5))
.unwrap();
assert_eq!(retry.user_id(), first.user_id());
assert_eq!(retry.person_anchor_id(), first.person_anchor_id());
assert_eq!(retry.full_name(), "Original");
assert_eq!(stack.ordering.tip(), retry_tip);
let (_, unused) = stack.invites.create().unwrap();
let conflict_tip = stack.ordering.tip();
assert_eq!(
stack
.users
.register(&unused, new_user("alice", "Must not exist", 6))
.err()
.unwrap(),
"username already registered"
);
assert_eq!(stack.ordering.tip(), conflict_tip);
assert_eq!(stack.invites.status(&unused).unwrap(), InviteStatus::Unused);
}
#[test]
fn person_changes_are_resolved_transitively() {
let root = TempDir::new().unwrap();
let stack = open(root.path());
let (_, code) = stack.invites.create().unwrap();
let registered = stack
.users
.register(&code, new_user("alice", "Original", 7))
.unwrap();
let user_id = registered.user_id();
let anchor = registered.person_anchor_id();
stack.persons.update(anchor, "Updated".to_owned()).unwrap();
let updated = stack.users.get(user_id).unwrap().unwrap();
assert_eq!(updated.person_anchor_id(), anchor);
assert_eq!(updated.person_id(), anchor);
assert_eq!(updated.full_name(), "Updated");
let middle = stack.persons.create("Middle".to_owned()).unwrap();
stack.persons.resolve(middle, anchor).unwrap();
let canonical = stack.persons.create("Canonical".to_owned()).unwrap();
stack.persons.resolve(canonical, middle).unwrap();
let resolved = stack.users.find_by_username("alice").unwrap().unwrap();
assert_eq!(resolved.user_id(), user_id);
assert_eq!(resolved.person_anchor_id(), anchor);
assert_eq!(resolved.person_id(), canonical);
assert_eq!(resolved.full_name(), "Canonical");
}
#[test]
fn concurrent_same_username_returns_one_user() {
let root = TempDir::new().unwrap();
let stack = open(root.path());
let (_, code) = stack.invites.create().unwrap();
let start = Arc::new(Barrier::new(5));
let workers: Vec<_> = (0..4)
.map(|_| {
let users = stack.users.clone();
let start = start.clone();
std::thread::spawn(move || {
start.wait();
users.register(&code, new_user("alice", "Concurrent", 8))
})
})
.collect();
start.wait();
let users: Vec<_> = workers
.into_iter()
.map(|worker| worker.join().unwrap().unwrap())
.collect();
let user_id = users[0].user_id();
let anchor = users[0].person_anchor_id();
for user in users {
assert_eq!(user.user_id(), user_id);
assert_eq!(user.person_anchor_id(), anchor);
assert_eq!(user.person_id(), anchor);
}
}