1use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8use std::sync::{Arc, Mutex, MutexGuard};
9
10use time::OffsetDateTime;
11
12use crate::audit::{self, Action, Actor, Entry};
13#[cfg(test)]
14use crate::backend::local::LocalConn;
15use crate::backend::local::{LocalRegistry, LocalTenantSpace};
16use crate::backend::{CommitError, Registry, RegistryTx, TenantRow, TenantSpace};
17use crate::clock::{Clock, from_nanos, nanos};
18use crate::envelope::{self, Context, Key, WrapLimit};
19use crate::fs::Fs;
20use crate::ids::TenantId;
21use crate::retention::Retention;
22use crate::store::{OpenError, Store};
23
24mod backup;
25mod examine;
26mod keys;
27mod rekey;
28
29pub use backup::{Backup, BackupError};
30pub use examine::{KeyOrder, RegistryExamination, RotationBacklog, TenantExamination};
31pub use keys::{KeyInfo, NewKey};
32pub(crate) use rekey::MasterStep;
33pub use rekey::rekey_master;
34#[cfg(test)]
35pub(crate) use tests::{Fixture, fixture};
36
37fn stored_retention(text: Option<String>) -> Result<Option<Retention>, TenantError> {
40 text.map(|text| Retention::parse(&text).ok_or(TenantError::Io))
41 .transpose()
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum TenantError {
47 NotFound,
49 NameTaken,
51 BadName,
53 HoldsVcons,
55 UnderHold,
58 LimitReached,
60 AuditFailed,
62 Closing,
64 CliOnly,
66 BadScopes,
68 WrongKey,
70 Io,
71}
72
73#[derive(Debug)]
77#[non_exhaustive]
78pub enum RegistryError {
79 WrongKey,
81 Failed {
83 step: OpenStep,
85 path: PathBuf,
87 source: Box<dyn std::error::Error + Send + Sync>,
89 },
90 Recovery(TenantError),
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[non_exhaustive]
97pub enum OpenStep {
98 CatalogKey,
100 OpenCatalog,
102 MakeTenantsDir,
104 SyncDataDir,
106}
107
108impl RegistryError {
109 pub(crate) fn failed(
110 step: OpenStep,
111 path: PathBuf,
112 source: impl std::error::Error + Send + Sync + 'static,
113 ) -> RegistryError {
114 RegistryError::Failed {
115 step,
116 path,
117 source: Box::new(source),
118 }
119 }
120}
121
122impl std::fmt::Display for RegistryError {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 RegistryError::WrongKey => {
126 f.write_str("the master key is not the one server.sqlite was made under")
127 }
128 RegistryError::Failed { step, path, .. } => {
129 let doing = match step {
130 OpenStep::CatalogKey => "the catalog key for",
131 OpenStep::OpenCatalog => "opening",
132 OpenStep::MakeTenantsDir => "making",
133 OpenStep::SyncDataDir => "syncing",
134 };
135 write!(f, "{doing} {} failed", path.display())
136 }
137 RegistryError::Recovery(e) => write!(f, "start-up recovery failed: {e:?}"),
138 }
139 }
140}
141
142impl std::error::Error for RegistryError {
143 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144 match self {
145 RegistryError::Failed { source, .. } => Some(source.as_ref()),
146 RegistryError::WrongKey | RegistryError::Recovery(_) => None,
147 }
148 }
149}
150
151pub(crate) fn with_causes(error: &dyn std::error::Error) -> String {
154 let mut text = error.to_string();
155 let mut under = error.source();
156 while let Some(cause) = under {
157 text.push_str(": ");
158 text.push_str(&cause.to_string());
159 under = cause.source();
160 }
161 text
162}
163
164impl From<RegistryError> for TenantError {
166 fn from(e: RegistryError) -> TenantError {
167 match e {
168 RegistryError::WrongKey => TenantError::WrongKey,
169 RegistryError::Failed { .. } => TenantError::Io,
170 RegistryError::Recovery(e) => e,
171 }
172 }
173}
174
175#[derive(Debug)]
180pub enum OpenFailure {
181 Refused(TenantError),
182 WrongKey,
183 Access {
184 path: PathBuf,
185 error: std::io::Error,
186 },
187}
188
189impl From<OpenFailure> for TenantError {
190 fn from(f: OpenFailure) -> TenantError {
191 match f {
192 OpenFailure::Refused(e) => e,
193 OpenFailure::WrongKey | OpenFailure::Access { .. } => TenantError::Io,
194 }
195 }
196}
197
198impl From<TenantError> for OpenFailure {
199 fn from(e: TenantError) -> OpenFailure {
200 OpenFailure::Refused(e)
201 }
202}
203
204impl From<OpenError> for OpenFailure {
205 fn from(e: OpenError) -> OpenFailure {
206 match e {
207 OpenError::WrongKey => OpenFailure::WrongKey,
208 OpenError::Access { path, error } => OpenFailure::Access { path, error },
209 OpenError::Store(_) => OpenFailure::Refused(TenantError::Io),
210 }
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct TenantInfo {
217 pub id: TenantId,
218 pub name: String,
219 pub retention: Option<Retention>,
221 pub created_at: OffsetDateTime,
222}
223
224impl TenantInfo {
225 pub fn to_json(&self) -> serde_json::Value {
228 serde_json::json!({
229 "id": self.id.to_string(),
230 "name": self.name,
231 "retention": self.retention.map(|r| r.to_string()),
232 })
233 }
234
235 pub fn kept_for(&self, default: Retention) -> Retention {
238 kept_for(self.retention, default)
239 }
240}
241
242pub(crate) fn kept_for(own: Option<Retention>, default: Retention) -> Retention {
245 own.unwrap_or(default)
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum Removal {
251 Empty,
253 Force,
255 ForceOverridingHolds,
257}
258
259enum Slot {
261 Open(Arc<Store>),
262 Closing,
264}
265
266pub struct Tenants {
268 space: Box<dyn TenantSpace>,
270 master: Key,
271 registry: Box<dyn Registry>,
272 stores: Mutex<HashMap<TenantId, Slot>>,
274 beside: bool,
276 fs: Arc<dyn Fs>,
277 clock: Arc<dyn Clock>,
278 #[cfg(test)]
279 hooks: tests::Hooks,
280}
281
282impl Tenants {
283 pub fn open(
287 dir: &Path,
288 master: Key,
289 fs: Arc<dyn Fs>,
290 clock: Arc<dyn Clock>,
291 ) -> Result<Tenants, RegistryError> {
292 let registry = Tenants::opened(dir, master, fs, clock, false)?;
293 registry.recover().map_err(RegistryError::Recovery)?;
294 Ok(registry)
295 }
296
297 pub fn open_beside(
302 dir: &Path,
303 master: Key,
304 fs: Arc<dyn Fs>,
305 clock: Arc<dyn Clock>,
306 ) -> Result<Tenants, RegistryError> {
307 Tenants::opened(dir, master, fs, clock, true)
308 }
309
310 fn opened(
311 dir: &Path,
312 master: Key,
313 fs: Arc<dyn Fs>,
314 clock: Arc<dyn Clock>,
315 beside: bool,
316 ) -> Result<Tenants, RegistryError> {
317 let catalog_key = envelope::catalog_key(&master).map_err(|e| {
318 RegistryError::failed(OpenStep::CatalogKey, dir.join("server.sqlite"), e)
319 })?;
320 let registry = LocalRegistry::open(dir, catalog_key)?;
321 let space = LocalTenantSpace::open(dir, fs.clone())?;
322 Ok(Tenants::with_backends(
323 Box::new(registry),
324 Box::new(space),
325 master,
326 fs,
327 clock,
328 beside,
329 ))
330 }
331
332 pub(crate) fn with_backends(
335 registry: Box<dyn Registry>,
336 space: Box<dyn TenantSpace>,
337 master: Key,
338 fs: Arc<dyn Fs>,
339 clock: Arc<dyn Clock>,
340 beside: bool,
341 ) -> Tenants {
342 Tenants {
343 space,
344 master,
345 registry,
346 stores: Mutex::new(HashMap::new()),
347 beside,
348 fs,
349 clock,
350 #[cfg(test)]
351 hooks: tests::Hooks::default(),
352 }
353 }
354
355 fn recover(&self) -> Result<(), TenantError> {
360 for id in self.space.present()? {
361 if self.registry.snapshot()?.tenant(id)?.is_none() {
362 self.space.remove(id)?;
363 }
364 }
365 Ok(())
366 }
367
368 pub fn create(
374 &self,
375 who: Actor,
376 name: &str,
377 retention: Option<Retention>,
378 ) -> Result<TenantId, TenantError> {
379 let id = TenantId::generate();
380 let entry = |status, target: Option<TenantId>| Entry {
381 actor: who,
382 action: Action::TenantCreate,
383 target: target.map(|t| t.to_string()),
384 version: None,
385 status,
386 };
387 let master_id = self.master.id();
388 {
389 let mut tx = self.registry.immediate()?;
390 let refused = if !good_name(name) {
391 Some((422, TenantError::BadName))
392 } else if tx.name_taken(name)? {
393 Some((409, TenantError::NameTaken))
394 } else if envelope::wrap_limit(tx.master_wraps(master_id)?) == WrapLimit::Refuse {
395 Some((503, TenantError::LimitReached))
396 } else {
397 None
398 };
399 if let Some((status, error)) = refused {
400 self.record(&mut tx, &entry(status, None))?;
401 commit(tx)?;
402 return Err(error);
403 }
404 tx.add_master_wrap(master_id, nanos(self.clock.now()))?;
407 commit(tx)?;
408 }
409
410 let tenant_key = Key::generate().map_err(|_| TenantError::Io)?;
411 let key_id = tenant_key.id();
412 let wrapped = envelope::wrap(&self.master, &Context::Tenant { tenant: id }, &tenant_key);
413 let unknown = std::cell::Cell::new(false);
417 let made = (|| {
418 self.space
419 .make(id, tenant_key, self.clock.clone())
420 .map_err(|_| TenantError::Io)?;
421 #[cfg(test)]
422 {
423 let hook = self.hooks.between.lock().expect("hook").take();
424 if let Some(hook) = hook {
425 hook();
426 }
427 }
428
429 let mut tx = self.registry.immediate()?;
430 if tx.name_taken(name)? {
431 self.record(&mut tx, &entry(409, None))?;
433 commit(tx)?;
434 return Err(TenantError::NameTaken);
435 }
436 let now = nanos(self.clock.now());
437 tx.insert_tenant(&TenantRow {
438 id,
439 name: name.to_owned(),
440 retention: retention.map(|r| r.to_string()),
441 created_at: now,
442 tenant_key_id: key_id,
443 wrapped: wrapped.clone(),
444 pending: None,
445 key_since: now,
446 })?;
447 self.record(&mut tx, &entry(201, Some(id)))?;
448 tx.commit().map_err(|e| {
449 unknown.set(e == CommitError::Unknown);
450 TenantError::Io
451 })?;
452 Ok(id)
453 })();
454 if made.is_err() && !unknown.get() {
459 let _ = self.space.remove(id);
460 }
461 made
462 }
463
464 pub fn list(&self, who: Actor) -> Result<Vec<TenantInfo>, TenantError> {
466 let mut tx = self.registry.immediate()?;
467 let tenants = tx
468 .tenants()?
469 .into_iter()
470 .map(|row| {
471 Ok(TenantInfo {
472 id: row.id,
473 name: row.name,
474 retention: stored_retention(row.retention)?,
475 created_at: from_nanos(row.created_at).ok_or(TenantError::Io)?,
476 })
477 })
478 .collect::<Result<Vec<_>, TenantError>>()?;
479 let entry = Entry {
480 actor: who,
481 action: Action::TenantList,
482 target: None,
483 version: None,
484 status: 200,
485 };
486 self.record(&mut tx, &entry)?;
487 commit(tx)?;
488 Ok(tenants)
489 }
490
491 pub fn set_retention(
494 &self,
495 who: Actor,
496 id: TenantId,
497 retention: Option<Retention>,
498 ) -> Result<(), TenantError> {
499 let entry = |action, status| Entry {
500 actor: who,
501 action,
502 target: Some(id.to_string()),
503 version: None,
504 status,
505 };
506 let closing = matches!(self.stores().get(&id), Some(Slot::Closing));
507 {
508 let mut tx = self.registry.immediate()?;
509 let text = retention.map(|r| r.to_string());
510 let refused = if closing {
511 Some((503, TenantError::Closing))
512 } else if !tx.set_retention(id, text.as_deref())? {
513 Some((404, TenantError::NotFound))
514 } else {
515 None
516 };
517 let status = refused.map_or(200, |(status, _)| status);
518 self.record(&mut tx, &entry(Action::TenantRetention, status))?;
519 commit(tx)?;
520 if let Some((_, error)) = refused {
521 return Err(error);
522 }
523 }
524 self.copy_to_tenant(id, &entry(Action::Retention, 200))
525 }
526
527 fn copy_to_tenant(&self, tenant: TenantId, entry: &Entry) -> Result<(), TenantError> {
533 let store = self.store(tenant).map_err(|_| TenantError::AuditFailed)?;
534 store.log(entry).map_err(|_| TenantError::AuditFailed)
535 }
536
537 pub fn remove(&self, who: Actor, id: TenantId, removal: Removal) -> Result<(), TenantError> {
544 let entry = |status| Entry {
545 actor: who,
546 action: Action::TenantRemove,
547 target: Some(id.to_string()),
548 version: None,
549 status,
550 };
551 let drained = match self.drain(id) {
552 Ok(drained) => drained,
553 Err(error) => {
554 self.record_alone(&entry(503))?;
555 return Err(error);
556 }
557 };
558 let result = self.remove_drained(id, removal, drained, entry);
559 if result.is_err() {
560 self.stores().remove(&id);
561 }
562 result
563 }
564
565 fn remove_drained(
566 &self,
567 id: TenantId,
568 removal: Removal,
569 drained: Option<Store>,
570 entry: impl Fn(u16) -> Entry,
571 ) -> Result<(), TenantError> {
572 let store = match drained {
573 Some(store) => store,
574 None => match self.open_store(id).map_err(TenantError::from) {
575 Err(TenantError::NotFound) => {
576 self.record_alone(&entry(404))?;
577 return Err(TenantError::NotFound);
578 }
579 other => other?,
580 },
581 };
582 let counts = store.counts().map_err(io)?;
583 let refused = match removal {
584 Removal::Empty if counts.versions > 0 => Some(TenantError::HoldsVcons),
585 Removal::Force if counts.held > 0 => Some(TenantError::UnderHold),
586 _ => None,
587 };
588 if let Some(error) = refused {
589 self.record_alone(&entry(409))?;
590 return Err(error);
591 }
592 {
593 let mut tx = self.registry.immediate()?;
594 tx.delete_tenant(id, nanos(self.clock.now()))?;
595 self.record(&mut tx, &entry(204))?;
596 commit(tx)?;
597 }
598 drop(store);
602 let _ = self.space.remove(id);
603 self.stores().remove(&id);
604 Ok(())
605 }
606
607 pub fn store(&self, id: TenantId) -> Result<Arc<Store>, TenantError> {
609 self.store_or_why(id).map_err(TenantError::from)
610 }
611
612 pub fn store_or_why(&self, id: TenantId) -> Result<Arc<Store>, OpenFailure> {
614 let mut stores = self.stores();
617 match stores.get(&id) {
618 Some(Slot::Open(store)) => return Ok(store.clone()),
619 Some(Slot::Closing) => return Err(TenantError::Closing.into()),
620 None => {}
621 }
622 let store = Arc::new(self.open_store(id)?);
623 stores.insert(id, Slot::Open(store.clone()));
624 Ok(store)
625 }
626
627 fn open_store(&self, id: TenantId) -> Result<Store, OpenFailure> {
631 let wrapped = self
632 .registry
633 .snapshot()?
634 .tenant(id)?
635 .ok_or(TenantError::NotFound)?
636 .wrapped;
637 let tenant_key = envelope::unwrap(&self.master, &Context::Tenant { tenant: id }, &wrapped)
638 .map_err(io)?;
639 #[cfg(test)]
640 {
641 let hook = self.hooks.opening.lock().expect("hook").take();
642 if let Some(hook) = hook {
643 hook();
644 }
645 }
646 self.space
647 .open(id, tenant_key, !self.beside, self.clock.clone())
648 .map_err(OpenFailure::from)
649 }
650
651 fn drain(&self, id: TenantId) -> Result<Option<Store>, TenantError> {
660 let slot = {
661 let mut stores = self.stores();
662 if matches!(stores.get(&id), Some(Slot::Closing)) {
663 return Err(TenantError::Closing);
664 }
665 stores.insert(id, Slot::Closing)
666 };
667 let Some(Slot::Open(mut store)) = slot else {
668 return Ok(None);
669 };
670 loop {
671 match Arc::try_unwrap(store) {
672 Ok(store) => return Ok(Some(store)),
673 Err(shared) => {
674 store = shared;
675 std::thread::sleep(std::time::Duration::from_millis(1));
676 }
677 }
678 }
679 }
680
681 fn stores(&self) -> MutexGuard<'_, HashMap<TenantId, Slot>> {
682 self.stores
683 .lock()
684 .expect("no thread panicked holding the stores")
685 }
686
687 #[cfg(test)]
689 pub(crate) fn break_audit(&self) {
690 self.conn()
691 .execute_batch(
692 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
693 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
694 )
695 .expect("trigger");
696 }
697
698 #[cfg(test)]
700 pub(crate) fn conn_for_test(&self) -> MutexGuard<'_, LocalConn> {
701 self.conn()
702 }
703
704 #[cfg(test)]
706 pub(crate) fn tenant_key_id_for_test(&self, id: TenantId) -> Vec<u8> {
707 let row = self.registry.snapshot().expect("reads").tenant(id);
708 let row = row.expect("reads").expect("the tenant's row");
709 row.tenant_key_id.bytes().to_vec()
710 }
711
712 #[cfg(test)]
714 pub(crate) fn admin_records(&self) -> Vec<serde_json::Value> {
715 crate::audit::testing::exported(&self.conn(), None)
716 }
717
718 #[cfg(test)]
721 pub(crate) fn close(&self, id: TenantId) {
722 self.drain(id).expect("not closing yet");
723 }
724
725 pub fn retention(&self, id: TenantId) -> Result<Option<Retention>, TenantError> {
730 let row = self.registry.snapshot()?.tenant(id)?;
731 stored_retention(row.ok_or(TenantError::NotFound)?.retention)
732 }
733
734 pub fn ids(&self) -> Result<Vec<TenantId>, TenantError> {
737 Ok(self
738 .registry
739 .snapshot()?
740 .tenants()?
741 .into_iter()
742 .map(|row| row.id)
743 .collect())
744 }
745
746 pub fn export_admin(
749 &self,
750 since: Option<i64>,
751 out: &mut dyn std::io::Write,
752 ) -> Result<(), audit::AuditError> {
753 self.registry.export(since.unwrap_or(0), out)
754 }
755
756 pub fn audit_page(
760 &self,
761 who: Actor,
762 query: &audit::AuditQuery,
763 ) -> Result<Vec<u8>, TenantError> {
764 let mut tx = self.registry.immediate()?;
765 let mut out = Vec::new();
766 let read = tx.audit_lines(query.after, Some(query.limit), &mut out);
767 self.record(
768 &mut tx,
769 &Entry {
770 actor: who,
771 action: Action::AuditRead,
772 target: None,
773 version: None,
774 status: if read.is_ok() { 200 } else { 500 },
775 },
776 )?;
777 commit(tx)?;
778 read.map(|()| out).map_err(io)
779 }
780
781 pub fn check(&self) -> Result<(), TenantError> {
783 self.registry.snapshot()?.tenants().map(|_| ())
784 }
785
786 pub(crate) fn record_alone(&self, entry: &Entry) -> Result<(), TenantError> {
788 let mut tx = self.registry.immediate()?;
789 self.record(&mut tx, entry)?;
790 commit(tx)
791 }
792
793 pub(crate) fn count_admin(&self, entry: &Entry) -> Result<(), TenantError> {
795 let mut tx = self.registry.immediate()?;
796 tx.count(entry, self.clock.now())
797 .map_err(|_| TenantError::AuditFailed)?;
798 commit(tx)
799 }
800
801 fn record(&self, tx: &mut RTx, entry: &Entry) -> Result<(), TenantError> {
802 tx.record(entry, self.clock.now())
803 .map_err(|_| TenantError::AuditFailed)
804 }
805
806 #[cfg(test)]
808 fn conn(&self) -> MutexGuard<'_, LocalConn> {
809 self.registry.local_conn()
810 }
811}
812
813fn good_name(name: &str) -> bool {
817 (1..=128).contains(&name.chars().count())
818 && !name.chars().any(char::is_control)
819 && name.trim() == name
820}
821
822pub(crate) type RTx<'a> = Box<dyn RegistryTx + 'a>;
824
825pub(crate) fn commit(tx: RTx) -> Result<(), TenantError> {
826 tx.commit().map_err(io)
827}
828
829pub(crate) fn io<E>(_: E) -> TenantError {
830 TenantError::Io
831}
832
833#[cfg(test)]
834mod tests {
835 use std::ffi::OsString;
836
837 use rusqlite::{Connection, OptionalExtension, params};
838
839 use serde_json::Value;
840 use time::macros::datetime;
841
842 use super::*;
843 use crate::audit::testing::exported;
844 use crate::catalog;
845 use crate::catalog::testing::columns_taking_null;
846 use crate::clock::ManualClock;
847 use crate::fs::{CrashFs, Op};
848 use crate::ids::VersionId;
849 use crate::store::testing::vcon;
850 use crate::store::{Caller, Created, StoreError};
851
852 const MARKER: &str = "plain-record-marker-8d21e4";
853 const UUID: &str = "01a07da8-c2bb-83e5-b9a2-279e0d16bc46";
854
855 fn caller() -> Caller {
856 Caller {
857 key: Key::generate().expect("key").id(),
858 }
859 }
860
861 pub(super) type RegistryHook = Box<dyn FnMut(&Tenants) + Send>;
863
864 #[derive(Default)]
865 pub(super) struct Hooks {
866 pub(super) between: Mutex<Option<Box<dyn FnOnce() + Send>>>,
869 pub(super) opening: Mutex<Option<Box<dyn FnOnce() + Send>>>,
871 pub(crate) rekey_stop: Mutex<Option<super::rekey::RekeyStep>>,
873 pub(super) registry_copied: Mutex<Option<RegistryHook>>,
876 }
877
878 struct Master {
880 holder: Key,
881 wrapped: Vec<u8>,
882 }
883
884 const CTX: Context = Context::Tenant {
885 tenant: TenantId([0; 16]),
886 };
887
888 impl Master {
889 fn new() -> Master {
890 let holder = Key::generate().expect("key");
891 let wrapped = envelope::wrap(&holder, &CTX, &Key::generate().expect("key"));
892 Master { holder, wrapped }
893 }
894
895 fn key(&self) -> Key {
896 envelope::unwrap(&self.holder, &CTX, &self.wrapped).expect("unwraps")
897 }
898 }
899
900 pub(crate) struct Fixture {
901 pub(crate) dir: tempfile::TempDir,
902 pub(crate) fs: Arc<CrashFs>,
903 pub(crate) clock: Arc<ManualClock>,
904 master: Master,
905 pub(crate) tenants: Tenants,
906 }
907
908 impl Fixture {
909 fn open(&self) -> Result<Tenants, RegistryError> {
910 Tenants::open(
911 self.dir.path(),
912 self.master.key(),
913 self.fs.clone(),
914 self.clock.clone(),
915 )
916 }
917
918 fn restart(&mut self) {
921 self.fs.crash();
922 let tenants = self.open().expect("opens again");
923 drop(std::mem::replace(&mut self.tenants, tenants));
924 }
925
926 pub(crate) fn conn(&self) -> MutexGuard<'_, Connection> {
927 self.tenants.conn()
928 }
929
930 pub(crate) fn master_key(&self) -> Key {
932 self.master.key()
933 }
934
935 pub(crate) fn tenants_dir(&self) -> PathBuf {
936 self.dir.path().join("tenants")
937 }
938
939 fn dirs(&self) -> Vec<OsString> {
941 self.fs.list(&self.tenants_dir()).expect("list tenants")
942 }
943
944 fn count(&self, sql: &str) -> i64 {
945 self.conn().query_row(sql, [], |r| r.get(0)).expect(sql)
946 }
947
948 pub(crate) fn master_wraps(&self) -> i64 {
949 self.count("SELECT coalesce(sum(wraps), 0) FROM master_key_use")
950 }
951
952 fn admin_log(&self) -> Vec<Value> {
953 exported(&self.conn(), None)
954 }
955
956 pub(crate) fn tenant_key(&self, id: TenantId) -> Key {
958 let wrapped: Vec<u8> = self
959 .conn()
960 .query_row(
961 "SELECT wrapped_tenant_key FROM tenant WHERE id = ?1",
962 [id.0],
963 |r| r.get(0),
964 )
965 .expect("a row");
966 envelope::unwrap(
967 &self.master.key(),
968 &Context::Tenant { tenant: id },
969 &wrapped,
970 )
971 .expect("unwraps under the master key")
972 }
973
974 pub(crate) fn store(&self, id: TenantId) -> Store {
976 Store::open(
977 &self.tenants_dir().join(id.to_string()),
978 id,
979 self.tenant_key(id),
980 self.fs.clone(),
981 self.clock.clone(),
982 )
983 .expect("the store opens")
984 }
985
986 fn has_row(&self, id: TenantId) -> bool {
987 self.conn()
988 .query_row("SELECT 1 FROM tenant WHERE id = ?1", [id.0], |_| Ok(()))
989 .optional()
990 .expect("query")
991 .is_some()
992 }
993
994 fn other(&self) -> Connection {
996 let catalog_key = envelope::catalog_key(&self.master.key()).expect("catalog key");
997 catalog::open(&self.dir.path().join("server.sqlite"), &catalog_key, "")
998 .expect("second connection")
999 }
1000 }
1001
1002 pub(crate) fn fixture() -> Fixture {
1003 let dir = tempfile::tempdir().expect("temp dir");
1004 let fs = Arc::new(CrashFs::new(dir.path()));
1005 let clock = Arc::new(ManualClock::at(datetime!(2026-10-23 12:00 UTC)));
1006 let master = Master::new();
1007 let tenants =
1008 Tenants::open(dir.path(), master.key(), fs.clone(), clock.clone()).expect("opens");
1009 Fixture {
1010 dir,
1011 fs,
1012 clock,
1013 master,
1014 tenants,
1015 }
1016 }
1017
1018 #[test]
1019 fn server_sqlite_is_a_catalog_under_the_master_keys_catalog_key() {
1020 let f = fixture();
1021 let conn = f.other();
1022 let tables: Vec<String> = conn
1023 .prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
1024 .expect("prepare")
1025 .query_map([], |r| r.get(0))
1026 .expect("query")
1027 .collect::<Result<_, _>>()
1028 .expect("names");
1029 assert_eq!(
1030 tables,
1031 [
1032 "api_key",
1033 "audit",
1034 "master_key_use",
1035 "sqlite_sequence",
1036 "tenant"
1037 ]
1038 );
1039 }
1040
1041 #[test]
1042 fn an_admin_audit_page_is_export_s_lines_and_records_its_read_after_them() {
1043 let f = fixture();
1044 for name in ["acme", "beta"] {
1045 f.tenants.create(Actor::Cli, name, None).expect("made");
1046 }
1047 let mut before = Vec::new();
1048 f.tenants.export_admin(None, &mut before).expect("export");
1049 let who = Actor::Key(Key::generate().expect("key").id());
1050 let query = |after, limit| audit::AuditQuery { after, limit };
1051 assert_eq!(
1052 f.tenants.audit_page(who, &query(0, 1000)),
1053 Ok(before.clone())
1054 );
1055 let read = exported(&f.conn(), None);
1056 let last = read.last().expect("records");
1057 assert_eq!(last["action"], "audit_read");
1058 assert_eq!(last["status"], 200);
1059 let lines: Vec<&[u8]> = before.split_inclusive(|b| *b == b'\n').collect();
1060 assert_eq!(
1061 f.tenants.audit_page(who, &query(1, 1)),
1062 Ok(lines[1].to_vec())
1063 );
1064 f.conn()
1065 .execute(
1066 "INSERT INTO audit (at, actor, action, status, count)
1067 VALUES ('2026-10-23T12:00:00Z', 'cli', 'read', 70000, 1)",
1068 [],
1069 )
1070 .expect("damaged");
1071 assert_eq!(
1072 f.tenants.audit_page(who, &query(0, 1000)),
1073 Err(TenantError::Io)
1074 );
1075 let last = exported_last(&f);
1076 assert_eq!(last["action"], "audit_read");
1077 assert_eq!(last["status"], 500);
1078 f.tenants.break_audit();
1079 assert_eq!(
1080 f.tenants.audit_page(who, &query(0, 1000)),
1081 Err(TenantError::AuditFailed)
1082 );
1083 }
1084
1085 fn exported_last(f: &Fixture) -> Value {
1087 let conn = f.conn();
1088 let (action, status): (String, i64) = conn
1089 .query_row(
1090 "SELECT action, status FROM audit ORDER BY seq DESC LIMIT 1",
1091 [],
1092 |r| Ok((r.get(0)?, r.get(1)?)),
1093 )
1094 .expect("a record");
1095 serde_json::json!({"action": action, "status": status})
1096 }
1097
1098 fn said(dir: &Path) -> String {
1101 let error = Tenants::open(
1102 dir,
1103 Key::generate().expect("key"),
1104 Arc::new(crate::fs::OsFs),
1105 Arc::new(ManualClock::at(datetime!(2026-10-23 12:00 UTC))),
1106 )
1107 .err()
1108 .expect("it does not open");
1109 format!("{error:?}")
1110 }
1111
1112 #[test]
1115 fn a_server_sqlite_that_cannot_be_opened_names_the_file_and_the_cause() {
1116 let dir = tempfile::tempdir().expect("temp dir");
1117 std::fs::create_dir(dir.path().join("server.sqlite")).expect("a directory in its place");
1118 let said = said(dir.path());
1119 assert!(said.contains("server.sqlite"), "the file is named: {said}");
1120 assert!(said.contains("OpenCatalog"), "the step is named: {said}");
1121 assert!(
1122 said.contains("unable to open database file"),
1123 "SQLite's own error is kept: {said}"
1124 );
1125 }
1126
1127 #[test]
1130 fn a_tenants_directory_that_cannot_be_made_names_it_and_the_cause() {
1131 let dir = tempfile::tempdir().expect("temp dir");
1132 std::fs::write(dir.path().join("tenants"), b"").expect("a file in its place");
1133 let said = said(dir.path());
1134 assert!(said.contains("tenants"), "the directory is named: {said}");
1135 assert!(said.contains("MakeTenantsDir"), "the step is named: {said}");
1136 assert!(
1137 said.contains("File exists"),
1138 "the system's error is kept: {said}"
1139 );
1140 }
1141
1142 #[test]
1143 fn a_wrong_master_key_does_not_open_server_sqlite() {
1144 let f = fixture();
1145 let wrong = Tenants::open(
1146 f.dir.path(),
1147 Master::new().key(),
1148 f.fs.clone(),
1149 f.clock.clone(),
1150 );
1151 assert!(
1152 matches!(wrong, Err(RegistryError::WrongKey)),
1153 "a wrong key is told apart from every other failure: {:?}",
1154 wrong.err()
1155 );
1156 assert!(f.open().is_ok(), "the right key still opens it");
1157 }
1158
1159 #[test]
1160 fn only_the_optional_columns_may_be_empty() {
1161 let f = fixture();
1162 let conn = f.conn();
1163 let tenant = [
1164 ("id", "x'11'"),
1165 ("name", "'a'"),
1166 ("retention", "'7y'"),
1167 ("created_at", "1"),
1168 ("tenant_key_id", "x'22'"),
1169 ("wrapped_tenant_key", "x'33'"),
1170 ("pending_wrapped_tenant_key", "x'44'"),
1171 ("key_since", "1"),
1172 ];
1173 let key_use = [("master_key_id", "x'55'"), ("wraps", "1"), ("since", "1")];
1174 let api_key = [
1175 ("id", "x'66'"),
1176 ("tenant", "x'11'"),
1177 ("token_hash", "x'77'"),
1178 ("scopes", "'read'"),
1179 ("label", "'a'"),
1180 ("created_at", "1"),
1181 ("revoked_at", "2"),
1182 ];
1183 let mut may_be_empty = columns_taking_null(&conn, "tenant", &tenant);
1184 may_be_empty.extend(columns_taking_null(&conn, "master_key_use", &key_use));
1185 may_be_empty.extend(columns_taking_null(&conn, "api_key", &api_key));
1186 assert_eq!(
1187 may_be_empty,
1188 [
1189 "tenant.retention",
1190 "tenant.pending_wrapped_tenant_key",
1191 "api_key.tenant",
1192 "api_key.revoked_at"
1193 ]
1194 );
1195 }
1196
1197 #[test]
1198 fn a_name_belongs_to_one_tenant() {
1199 let f = fixture();
1200 let conn = f.conn();
1201 let add = |id: u8, name: &str| {
1202 conn.execute(
1203 "INSERT INTO tenant (id, name, created_at, tenant_key_id,
1204 wrapped_tenant_key, key_since)
1205 VALUES (?1, ?2, 1, x'22', x'33', 1)",
1206 params![[id; 16], name],
1207 )
1208 };
1209 add(1, "a").expect("the first tenant");
1210 add(2, "b").expect("another name");
1211 assert!(add(3, "a").is_err(), "a second tenant named a");
1212 }
1213
1214 #[test]
1217 fn no_plain_record_reaches_server_sqlite_or_its_wal() {
1218 let f = fixture();
1219 f.tenants
1220 .create(Actor::Cli, MARKER, Some(Retention::Years(7)))
1221 .expect("a tenant");
1222 let wal = std::fs::metadata(f.dir.path().join("server.sqlite-wal")).expect("a WAL");
1223 assert!(wal.len() > 0, "the commits are in the WAL");
1224 for entry in std::fs::read_dir(f.dir.path()).expect("list") {
1225 let file = entry.expect("entry").path();
1226 if !file.is_file() {
1227 continue;
1228 }
1229 let bytes = std::fs::read(&file).expect("read");
1230 assert!(
1231 !bytes.windows(MARKER.len()).any(|w| w == MARKER.as_bytes()),
1232 "plain record in {}",
1233 file.display()
1234 );
1235 }
1236 }
1237
1238 #[test]
1239 fn a_new_registrys_tenants_directory_survives_a_crash() {
1240 let f = fixture();
1241 f.fs.crash();
1242 assert_eq!(
1243 f.fs.list(f.dir.path()).expect("list"),
1244 [OsString::from("tenants")]
1245 );
1246 }
1247
1248 struct Row {
1250 name: String,
1251 retention: Option<String>,
1252 created_at: i64,
1253 key_id: Vec<u8>,
1254 pending: Option<Vec<u8>>,
1255 key_since: i64,
1256 }
1257
1258 #[test]
1259 fn a_tenant_is_made_with_its_key_directory_catalog_and_row() {
1260 let f = fixture();
1261 let id = f
1262 .tenants
1263 .create(Actor::Cli, "acme", Some(Retention::Years(7)))
1264 .expect("made");
1265 f.clock.set(datetime!(2026-10-23 13:00 UTC));
1266 let other = f.tenants.create(Actor::Cli, "lab", None).expect("another");
1267 assert_ne!(id, other, "a new id each time");
1268
1269 let Row {
1270 name,
1271 retention,
1272 created_at,
1273 key_id,
1274 pending,
1275 key_since,
1276 } = f
1277 .conn()
1278 .query_row(
1279 "SELECT name, retention, created_at, tenant_key_id,
1280 pending_wrapped_tenant_key, key_since
1281 FROM tenant WHERE id = ?1",
1282 [id.0],
1283 |r| {
1284 Ok(Row {
1285 name: r.get(0)?,
1286 retention: r.get(1)?,
1287 created_at: r.get(2)?,
1288 key_id: r.get(3)?,
1289 pending: r.get(4)?,
1290 key_since: r.get(5)?,
1291 })
1292 },
1293 )
1294 .expect("a row");
1295 let now = nanos(datetime!(2026-10-23 12:00 UTC));
1296 assert_eq!(name, "acme");
1297 assert_eq!(retention.as_deref(), Some("7y"));
1298 assert_eq!((created_at, key_since), (now, now));
1299 assert_eq!(pending, None);
1300 let key = f.tenant_key(id);
1301 assert_eq!(key_id, key.id().bytes());
1302 let lab_retention: Option<String> = f
1303 .conn()
1304 .query_row(
1305 "SELECT retention FROM tenant WHERE id = ?1",
1306 [other.0],
1307 |r| r.get(0),
1308 )
1309 .expect("a row");
1310 assert_eq!(lab_retention, None, "no period means the server default");
1311
1312 let mut dirs = vec![
1313 OsString::from(id.to_string()),
1314 OsString::from(other.to_string()),
1315 ];
1316 dirs.sort();
1317 assert_eq!(f.dirs(), dirs);
1318 drop(f.store(id));
1319 let catalog_key = envelope::catalog_key(&key).expect("catalog key");
1320 let tenant_dir = f.tenants_dir().join(id.to_string());
1321 assert!(
1322 catalog::open(&tenant_dir.join("catalog.sqlite"), &catalog_key, "").is_ok(),
1323 "the catalog is under the tenant key's catalog key"
1324 );
1325
1326 let (wraps, since): (i64, i64) = f
1327 .conn()
1328 .query_row(
1329 "SELECT wraps, since FROM master_key_use WHERE master_key_id = ?1",
1330 [f.master.key().id().bytes()],
1331 |r| Ok((r.get(0)?, r.get(1)?)),
1332 )
1333 .expect("the master key's use");
1334 assert_eq!((wraps, since), (2, now));
1335
1336 let log = f.admin_log();
1337 assert_eq!(log.len(), 2);
1338 assert_eq!(log[0]["actor"], "cli");
1339 assert_eq!(log[0]["action"], "tenant_create");
1340 assert_eq!(log[0]["target"], id.to_string());
1341 assert_eq!(log[0]["status"], 201);
1342 assert_eq!(log[0]["at"], "2026-10-23T12:00:00Z");
1343 }
1344
1345 #[test]
1346 fn a_taken_name_is_refused_before_anything_is_made() {
1347 let f = fixture();
1348 f.tenants.create(Actor::Cli, "acme", None).expect("made");
1349 let ops = f.fs.log().len();
1350 assert_eq!(
1351 f.tenants.create(Actor::Cli, "acme", None),
1352 Err(TenantError::NameTaken)
1353 );
1354 assert_eq!(f.fs.log().len(), ops, "nothing made");
1355 assert_eq!(f.master_wraps(), 1, "nothing wrapped");
1356 assert_eq!(f.count("SELECT count(*) FROM tenant"), 1);
1357 let log = f.admin_log();
1358 assert_eq!(log[1]["action"], "tenant_create");
1359 assert_eq!(log[1]["status"], 409);
1360 assert_eq!(log[1]["target"], Value::Null, "no tenant was made");
1361 }
1362
1363 #[test]
1364 fn a_name_is_1_to_128_characters_without_controls_or_outer_spaces() {
1365 let long = "é".repeat(128);
1366 for name in ["acme", "a", "Acme Corp", "Acme — Europe", long.as_str()] {
1367 assert!(good_name(name), "{name:?}");
1368 }
1369 let longer = "é".repeat(129);
1370 for name in [
1371 "",
1372 " acme",
1373 "acme ",
1374 "ac\nme",
1375 "ac\tme",
1376 "acme\u{7f}",
1377 "\u{85}acme",
1378 "acme\u{3000}",
1379 longer.as_str(),
1380 ] {
1381 assert!(!good_name(name), "{name:?}");
1382 }
1383 }
1384
1385 #[test]
1386 fn a_bad_name_is_refused_and_recorded_before_anything_is_made() {
1387 let f = fixture();
1388 let ops = f.fs.log().len();
1389 assert_eq!(
1390 f.tenants.create(Actor::Cli, " acme", None),
1391 Err(TenantError::BadName)
1392 );
1393 assert_eq!(f.fs.log().len(), ops, "nothing made");
1394 assert_eq!(f.master_wraps(), 0, "nothing wrapped");
1395 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0);
1396 let log = f.admin_records("tenant_create");
1397 assert_eq!(log.len(), 1);
1398 assert_eq!(log[0]["status"], 422);
1399 assert_eq!(log[0]["target"], Value::Null, "no tenant was made");
1400 }
1401
1402 #[test]
1403 fn a_master_key_at_its_wrap_limit_makes_no_tenant() {
1404 let f = fixture();
1405 f.conn()
1406 .execute(
1407 "INSERT INTO master_key_use (master_key_id, wraps, since) VALUES (?1, ?2, 1)",
1408 params![f.master.key().id().bytes(), 1_i64 << 32],
1409 )
1410 .expect("at the limit");
1411 let ops = f.fs.log().len();
1412 assert_eq!(
1413 f.tenants.create(Actor::Cli, "acme", None),
1414 Err(TenantError::LimitReached)
1415 );
1416 assert_eq!(f.fs.log().len(), ops, "nothing made");
1417 assert_eq!(f.master_wraps(), 1_i64 << 32, "nothing counted");
1418 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0);
1419 assert_eq!(f.admin_log()[0]["status"], 503);
1420 }
1421
1422 #[test]
1423 fn a_negative_wrap_count_is_damage_and_makes_no_tenant() {
1424 let f = fixture();
1425 f.conn()
1426 .execute(
1427 "INSERT INTO master_key_use (master_key_id, wraps, since) VALUES (?1, -1, 1)",
1428 [f.master.key().id().bytes()],
1429 )
1430 .expect("a damaged count");
1431 let ops = f.fs.log().len();
1432 assert_eq!(
1433 f.tenants.create(Actor::Cli, "acme", None),
1434 Err(TenantError::Io)
1435 );
1436 assert_eq!(f.fs.log().len(), ops, "nothing made");
1437 }
1438
1439 #[test]
1440 fn a_name_taken_while_the_directory_is_made_loses_and_leaves_nothing() {
1441 let f = fixture();
1442 let other = f.other();
1443 *f.tenants.hooks.between.lock().expect("hook") = Some(Box::new(move || {
1444 other
1445 .execute(
1446 "INSERT INTO tenant (id, name, created_at, tenant_key_id,
1447 wrapped_tenant_key, key_since)
1448 VALUES (x'01', 'acme', 1, x'02', x'03', 1)",
1449 [],
1450 )
1451 .expect("another create wins the name");
1452 }));
1453 assert_eq!(
1454 f.tenants.create(Actor::Cli, "acme", None),
1455 Err(TenantError::NameTaken)
1456 );
1457 assert_eq!(f.dirs(), Vec::<OsString>::new(), "its directory is removed");
1458 assert_eq!(f.master_wraps(), 1, "its wrap is still counted");
1459 let log = f.admin_log();
1460 assert_eq!(log.len(), 1);
1461 assert_eq!(log[0]["status"], 409);
1462 }
1463
1464 #[test]
1465 fn a_create_whose_audit_record_fails_leaves_nothing() {
1466 let f = fixture();
1467 f.conn()
1468 .execute_batch(
1469 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
1470 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
1471 )
1472 .expect("trigger");
1473 assert_eq!(
1474 f.tenants.create(Actor::Cli, "acme", None),
1475 Err(TenantError::AuditFailed)
1476 );
1477 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0);
1478 assert_eq!(f.dirs(), Vec::<OsString>::new(), "its directory is removed");
1479 }
1480
1481 #[test]
1482 fn a_create_whose_row_cannot_be_written_leaves_nothing() {
1483 let f = fixture();
1484 f.conn()
1485 .execute_batch(
1486 "CREATE TEMP TRIGGER no_row BEFORE INSERT ON tenant
1487 BEGIN SELECT RAISE(ABORT, 'the disk has failed'); END;",
1488 )
1489 .expect("trigger");
1490 assert_eq!(
1491 f.tenants.create(Actor::Cli, "acme", None),
1492 Err(TenantError::Io)
1493 );
1494 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0);
1495 assert_eq!(f.dirs(), Vec::<OsString>::new(), "its directory is removed");
1496 }
1497
1498 #[test]
1499 fn a_create_whose_commit_outcome_is_unknown_keeps_its_directory() {
1500 let f = fixture();
1501 f.tenants.registry.fail_commit(2, CommitError::Unknown);
1505 assert_eq!(
1506 f.tenants.create(Actor::Cli, "acme", None),
1507 Err(TenantError::Io),
1508 "a_create_whose_commit_outcome_is_unknown_keeps_its_directory: f.tenants.create(Actor::Cli, \"acme\", None)"
1509 );
1510 assert_eq!(
1511 f.count("SELECT count(*) FROM tenant"),
1512 1,
1513 "a_create_whose_commit_outcome_is_unknown_keeps_its_directory: f.count(\"SELECT count(*) FROM tenant\")"
1514 );
1515 assert_eq!(
1516 f.dirs().len(),
1517 1,
1518 "the directory made for the kept row stays"
1519 );
1520 let id = f.tenants.ids().expect("ids")[0];
1521 let store = f.tenants.store(id).expect("the kept tenant opens");
1522 let (found, body) = vcon(UUID, "kept");
1523 store.create(&caller(), &found, &body).expect("stores");
1524 }
1525
1526 #[test]
1527 fn a_create_whose_commit_rolls_back_leaves_nothing() {
1528 let f = fixture();
1529 f.tenants.registry.fail_commit(2, CommitError::RolledBack);
1530 assert_eq!(
1531 f.tenants.create(Actor::Cli, "acme", None),
1532 Err(TenantError::Io),
1533 "a_create_whose_commit_rolls_back_leaves_nothing: f.tenants.create(Actor::Cli, \"acme\", None)"
1534 );
1535 assert_eq!(
1536 f.count("SELECT count(*) FROM tenant"),
1537 0,
1538 "a_create_whose_commit_rolls_back_leaves_nothing: f.count(\"SELECT count(*) FROM tenant\")"
1539 );
1540 assert_eq!(f.dirs(), Vec::<OsString>::new(), "its directory is removed");
1541 }
1542
1543 #[test]
1544 fn a_refusal_whose_audit_record_fails_is_an_audit_failure() {
1545 let f = fixture();
1546 f.tenants.create(Actor::Cli, "acme", None).expect("made");
1547 f.conn()
1548 .execute_batch(
1549 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
1550 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
1551 )
1552 .expect("trigger");
1553 assert_eq!(
1554 f.tenants.create(Actor::Cli, "acme", None),
1555 Err(TenantError::AuditFailed)
1556 );
1557 }
1558
1559 const CREATE_STEPS: usize = 4;
1563
1564 #[test]
1565 fn a_create_writes_its_directories_in_order() {
1566 let f = fixture();
1567 let ops = f.fs.log().len();
1568 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1569 let dir = f.tenants_dir().join(id.to_string());
1570 assert_eq!(
1571 f.fs.log()[ops..],
1572 [
1573 Op::CreateDir(dir.clone()),
1574 Op::CreateDir(dir.join("objects")),
1575 Op::SyncDir(dir),
1576 Op::SyncDir(f.tenants_dir()),
1577 ]
1578 );
1579 assert_eq!(f.fs.log().len() - ops, CREATE_STEPS);
1580 }
1581
1582 #[test]
1583 fn a_create_cut_off_at_any_step_leaves_a_whole_tenant_or_none() {
1584 for step in 0..=CREATE_STEPS {
1585 let mut f = fixture();
1586 f.fs.fail_from(f.fs.log().len() + step);
1587 let result = f.tenants.create(Actor::Cli, "acme", None);
1588 assert_eq!(
1589 result.is_ok(),
1590 step == CREATE_STEPS,
1591 "step {step}: {result:?}"
1592 );
1593 f.restart();
1594 match result {
1595 Ok(id) => {
1596 assert!(f.has_row(id), "step {step}");
1597 assert_eq!(f.dirs(), [OsString::from(id.to_string())]);
1598 let store = f.store(id);
1599 drop(store);
1600 }
1601 Err(_) => {
1602 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0, "step {step}");
1603 assert_eq!(f.dirs(), Vec::<OsString>::new(), "step {step}");
1604 }
1605 }
1606 }
1607 }
1608
1609 #[test]
1610 fn a_create_cut_off_before_its_commit_leaves_no_tenant() {
1611 let mut f = fixture();
1612 let other = f.other();
1613 let fs = f.fs.clone();
1614 *f.tenants.hooks.between.lock().expect("hook") = Some(Box::new(move || {
1615 fs.fail_from(0);
1616 other
1617 .execute_batch(
1618 "CREATE TRIGGER dead BEFORE INSERT ON audit
1619 BEGIN SELECT RAISE(ABORT, 'the process has died'); END;",
1620 )
1621 .expect("trigger");
1622 }));
1623 assert!(f.tenants.create(Actor::Cli, "acme", None).is_err());
1624 assert_eq!(f.dirs().len(), 1, "the dead process removed nothing");
1625 f.conn()
1626 .execute_batch("DROP TRIGGER dead")
1627 .expect("drop trigger");
1628 f.restart();
1629 assert_eq!(f.count("SELECT count(*) FROM tenant"), 0);
1630 assert_eq!(f.dirs(), Vec::<OsString>::new(), "removed at start");
1631 }
1632
1633 #[test]
1634 fn opening_removes_only_tenant_directories_with_no_row() {
1635 let mut f = fixture();
1636 let kept = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1637 let orphan = TenantId::generate();
1638 let tenants = f.tenants_dir();
1639 for name in [orphan.to_string(), "notes".to_owned()] {
1640 f.fs.create_dir_all(&tenants.join(name))
1641 .expect("a directory");
1642 }
1643 f.fs.sync_dir(&tenants).expect("sync");
1644 f.restart();
1645 assert_eq!(
1646 f.dirs(),
1647 [OsString::from(kept.to_string()), OsString::from("notes")]
1648 );
1649 drop(f.store(kept));
1650 }
1651
1652 #[test]
1655 fn a_servers_registry_opens_each_store_with_recovery() {
1656 let f = fixture();
1657 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1658 let objects = f.tenants_dir().join(id.to_string()).join("objects");
1659 let leftover = format!("{}.tmp", VersionId([0x0e; 16]));
1660 f.fs.write_new(&objects.join(&leftover), b"x")
1661 .expect("write");
1662 let tenants = f.open().expect("opens again");
1663 drop(tenants.store(id).expect("its store"));
1664 assert!(
1665 !f.fs
1666 .list(&objects)
1667 .expect("list")
1668 .contains(&OsString::from(leftover)),
1669 "the leftover is removed"
1670 );
1671 }
1672
1673 #[test]
1677 fn opening_beside_a_server_removes_nothing() {
1678 let f = fixture();
1679 let kept = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1680 let orphan = TenantId::generate();
1681 let tenants = f.tenants_dir();
1682 f.fs.create_dir_all(&tenants.join(orphan.to_string()))
1683 .expect("a directory");
1684 f.fs.sync_dir(&tenants).expect("sync");
1685 let in_progress = tenants
1686 .join(kept.to_string())
1687 .join("objects")
1688 .join(format!("{}.tmp", VersionId([0x0e; 16])));
1689 f.fs.write_new(&in_progress, b"x").expect("write");
1690 let beside =
1691 Tenants::open_beside(f.dir.path(), f.master.key(), f.fs.clone(), f.clock.clone())
1692 .expect("opens beside");
1693 let mut expected = [
1694 OsString::from(kept.to_string()),
1695 OsString::from(orphan.to_string()),
1696 ];
1697 expected.sort();
1698 assert_eq!(f.dirs(), expected);
1699 drop(beside.store(kept).expect("its store"));
1700 assert!(
1701 f.fs.list(in_progress.parent().expect("objects"))
1702 .expect("list")
1703 .contains(&OsString::from(in_progress.file_name().expect("name"))),
1704 "the write in progress stays"
1705 );
1706 }
1707
1708 #[test]
1709 fn an_unknown_tenant_has_no_store() {
1710 let f = fixture();
1711 assert_eq!(
1712 f.tenants.store(TenantId::generate()).err(),
1713 Some(TenantError::NotFound)
1714 );
1715 }
1716
1717 #[test]
1718 fn every_request_to_a_tenant_shares_one_store() {
1719 let f = fixture();
1720 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1721 let a = f.tenants.store(id).expect("a store");
1722 let b = f.tenants.store(id).expect("the same store");
1723 assert!(Arc::ptr_eq(&a, &b));
1724 let (found, body) = vcon(UUID, "first");
1725 a.create(&caller(), &found, &body).expect("stored");
1726 assert_eq!(b.get(&caller(), found.uuid).expect("read").body, body);
1727 }
1728
1729 #[test]
1732 fn a_store_is_opened_once_even_by_requests_at_the_same_time() {
1733 let f = fixture();
1734 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1735 let (opening, is_opening) = std::sync::mpsc::channel();
1736 let (go, wait) = std::sync::mpsc::channel::<()>();
1737 *f.tenants.hooks.opening.lock().expect("hook") = Some(Box::new(move || {
1738 opening.send(()).expect("send");
1739 wait.recv().expect("go");
1740 }));
1741 let tenants = &f.tenants;
1744 std::thread::scope(move |s| {
1745 let first = s.spawn(move || tenants.store(id).expect("the first"));
1746 is_opening
1747 .recv_timeout(std::time::Duration::from_secs(10))
1748 .expect("the first is opening");
1749 let (second_done, second) = std::sync::mpsc::channel();
1750 s.spawn(move || {
1751 second_done
1752 .send(tenants.store(id).expect("the second"))
1753 .expect("send");
1754 });
1755 assert!(
1756 second
1757 .recv_timeout(std::time::Duration::from_millis(100))
1758 .is_err(),
1759 "the second waits while the first opens"
1760 );
1761 go.send(()).expect("go");
1762 let first = first.join().expect("the first returns");
1763 let second = second.recv().expect("the second returns");
1764 assert!(Arc::ptr_eq(&first, &second));
1765 });
1766 }
1767
1768 #[test]
1769 fn a_tenant_key_that_does_not_unwrap_opens_no_store() {
1770 let f = fixture();
1771 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1772 f.conn()
1773 .execute(
1774 "UPDATE tenant SET wrapped_tenant_key = x'00' WHERE id = ?1",
1775 [id.0],
1776 )
1777 .expect("damage the wrapped key");
1778 assert_eq!(f.tenants.store(id).err(), Some(TenantError::Io));
1779 }
1780
1781 #[test]
1782 fn draining_refuses_new_requests_and_waits_for_those_in_flight() {
1783 let f = fixture();
1784 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1785 let (found, body) = vcon(UUID, "first");
1786 let held = f.tenants.store(id).expect("a request in flight");
1787 std::thread::scope(|s| {
1788 let (done, drained) = std::sync::mpsc::channel();
1789 let tenants = &f.tenants;
1790 s.spawn(move || {
1791 tenants.drain(id).expect("drains");
1792 done.send(()).expect("send");
1793 });
1794 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
1795 while f.tenants.store(id).is_ok() {
1796 assert!(std::time::Instant::now() < deadline, "never closing");
1797 std::thread::yield_now();
1798 }
1799 assert_eq!(f.tenants.store(id).err(), Some(TenantError::Closing));
1800 assert!(
1801 drained
1802 .recv_timeout(std::time::Duration::from_millis(100))
1803 .is_err(),
1804 "the drain waits for the request in flight"
1805 );
1806 held.create(&caller(), &found, &body)
1807 .expect("the request in flight finishes");
1808 drop(held);
1809 drained
1810 .recv()
1811 .expect("the drain ends once the last handle is dropped");
1812 });
1813 assert_eq!(f.tenants.store(id).err(), Some(TenantError::Closing));
1814 }
1815
1816 #[test]
1817 fn a_tenant_no_request_opened_drains_at_once() {
1818 let f = fixture();
1819 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1820 f.tenants.drain(id).expect("drains");
1821 assert_eq!(f.tenants.store(id).err(), Some(TenantError::Closing));
1822 }
1823
1824 impl Fixture {
1825 fn own_retention(&self, id: TenantId) -> Option<String> {
1827 self.conn()
1828 .query_row("SELECT retention FROM tenant WHERE id = ?1", [id.0], |r| {
1829 r.get(0)
1830 })
1831 .expect("a row")
1832 }
1833
1834 pub(crate) fn tenant_catalog(&self, id: TenantId) -> Connection {
1836 let catalog_key = envelope::catalog_key(&self.tenant_key(id)).expect("catalog key");
1837 catalog::open(
1838 &self
1839 .tenants_dir()
1840 .join(id.to_string())
1841 .join("catalog.sqlite"),
1842 &catalog_key,
1843 "",
1844 )
1845 .expect("the tenant's catalog")
1846 }
1847
1848 pub(crate) fn admin_records(&self, action: &str) -> Vec<Value> {
1850 self.admin_log()
1851 .into_iter()
1852 .filter(|r| r["action"] == action)
1853 .collect()
1854 }
1855 }
1856
1857 #[test]
1859 fn retention_falls_back_to_the_server_default_when_unset() {
1860 let info = |retention| TenantInfo {
1861 id: TenantId([1; 16]),
1862 name: "acme".to_owned(),
1863 retention,
1864 created_at: datetime!(2026-10-23 12:00 UTC),
1865 };
1866 assert_eq!(
1867 info(None).kept_for(Retention::Years(7)),
1868 Retention::Years(7)
1869 );
1870 assert_eq!(
1871 info(Some(Retention::Days(90))).kept_for(Retention::Years(7)),
1872 Retention::Days(90)
1873 );
1874 }
1875
1876 #[test]
1877 fn a_tenant_retention_is_read_afresh_and_not_recorded() {
1878 let f = fixture();
1879 let lab = f
1880 .tenants
1881 .create(Actor::Cli, "lab", Some(Retention::Months(18)))
1882 .expect("made");
1883 let acme = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1884 let records = f.tenants.admin_records().len();
1885
1886 assert_eq!(f.tenants.retention(lab), Ok(Some(Retention::Months(18))));
1887 assert_eq!(f.tenants.retention(acme), Ok(None));
1888 f.tenants
1889 .set_retention(Actor::Cli, acme, Some(Retention::Days(0)))
1890 .expect("changed");
1891 assert_eq!(f.tenants.retention(acme), Ok(Some(Retention::Days(0))));
1892 assert_eq!(
1893 f.tenants.retention(TenantId([0xee; 16])),
1894 Err(TenantError::NotFound)
1895 );
1896 assert_eq!(
1897 f.tenants.admin_records().len(),
1898 records + 1,
1899 "only the retention change is recorded"
1900 );
1901 f.tenants
1902 .conn_for_test()
1903 .execute("UPDATE tenant SET retention = '7w' WHERE id = ?1", [lab.0])
1904 .expect("damaged");
1905 assert_eq!(
1906 f.tenants.retention(lab),
1907 Err(TenantError::Io),
1908 "a period that does not read back is damage"
1909 );
1910 }
1911
1912 #[test]
1913 fn ids_names_every_tenant_and_records_nothing() {
1914 let f = fixture();
1915 let a = f.tenants.create(Actor::Cli, "a", None).expect("made");
1916 let b = f.tenants.create(Actor::Cli, "b", None).expect("made");
1917 let records = f.tenants.admin_records().len();
1918
1919 let mut ids = f.tenants.ids().expect("ids");
1920 ids.sort_by_key(|id| id.0);
1921 let mut made = vec![a, b];
1922 made.sort_by_key(|id| id.0);
1923
1924 assert_eq!(ids, made);
1925 assert_eq!(f.tenants.admin_records().len(), records, "nothing recorded");
1926 }
1927
1928 #[test]
1929 fn list_shows_every_tenant_by_name_and_is_recorded() {
1930 let f = fixture();
1931 assert_eq!(f.tenants.list(Actor::Cli), Ok(Vec::new()));
1932 let lab = f
1933 .tenants
1934 .create(Actor::Cli, "lab", Some(Retention::Days(90)))
1935 .expect("made");
1936 f.clock.set(datetime!(2026-10-23 13:00 UTC));
1937 let acme = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1938 assert_eq!(
1939 f.tenants.list(Actor::Cli),
1940 Ok(vec![
1941 TenantInfo {
1942 id: acme,
1943 name: "acme".to_owned(),
1944 retention: None,
1945 created_at: datetime!(2026-10-23 13:00 UTC),
1946 },
1947 TenantInfo {
1948 id: lab,
1949 name: "lab".to_owned(),
1950 retention: Some(Retention::Days(90)),
1951 created_at: datetime!(2026-10-23 12:00 UTC),
1952 },
1953 ])
1954 );
1955 let lists = f.admin_records("tenant_list");
1956 assert_eq!(lists.len(), 2, "one record per list");
1957 for record in lists {
1958 assert_eq!(record["actor"], "cli");
1959 assert_eq!(record["status"], 200);
1960 assert_eq!(record["target"], Value::Null);
1961 }
1962 }
1963
1964 #[test]
1965 fn a_stored_period_that_does_not_read_back_is_damage() {
1966 let f = fixture();
1967 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1968 f.conn()
1969 .execute("UPDATE tenant SET retention = '7w' WHERE id = ?1", [id.0])
1970 .expect("damage the period");
1971 assert_eq!(f.tenants.list(Actor::Cli), Err(TenantError::Io));
1972 }
1973
1974 #[test]
1975 fn a_list_whose_record_fails_shows_nothing() {
1976 let f = fixture();
1977 f.tenants.create(Actor::Cli, "acme", None).expect("made");
1978 f.conn()
1979 .execute_batch(
1980 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
1981 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
1982 )
1983 .expect("trigger");
1984 assert_eq!(f.tenants.list(Actor::Cli), Err(TenantError::AuditFailed));
1985 }
1986
1987 #[test]
1988 fn a_retention_change_is_kept_and_recorded_in_both_logs() {
1989 let f = fixture();
1990 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
1991 f.clock.set(datetime!(2026-10-23 13:00 UTC));
1992 f.tenants
1993 .set_retention(Actor::Cli, id, Some(Retention::Months(18)))
1994 .expect("set");
1995 assert_eq!(f.own_retention(id).as_deref(), Some("18m"));
1996 f.tenants
1997 .set_retention(Actor::Cli, id, None)
1998 .expect("back to the default");
1999 assert_eq!(f.own_retention(id), None);
2000
2001 let admin = f.admin_records("tenant_retention");
2002 assert_eq!(admin.len(), 2);
2003 for record in &admin {
2004 assert_eq!(record["actor"], "cli");
2005 assert_eq!(record["target"], id.to_string());
2006 assert_eq!(record["status"], 200);
2007 assert_eq!(record["at"], "2026-10-23T13:00:00Z");
2008 }
2009 let copies: Vec<Value> = exported(&f.tenant_catalog(id), None)
2010 .into_iter()
2011 .filter(|r| r["action"] == "retention")
2012 .collect();
2013 assert_eq!(copies.len(), 2, "a copy in the tenant's log for each");
2014 for record in &copies {
2015 assert_eq!(record["actor"], "cli");
2016 assert_eq!(record["target"], id.to_string());
2017 assert_eq!(record["status"], 200);
2018 }
2019 }
2020
2021 #[test]
2022 fn a_retention_change_for_an_unknown_tenant_is_not_found() {
2023 let f = fixture();
2024 let id = TenantId::generate();
2025 assert_eq!(
2026 f.tenants
2027 .set_retention(Actor::Cli, id, Some(Retention::Years(1))),
2028 Err(TenantError::NotFound)
2029 );
2030 let admin = f.admin_records("tenant_retention");
2031 assert_eq!(admin.len(), 1);
2032 assert_eq!(admin[0]["status"], 404);
2033 assert_eq!(admin[0]["target"], id.to_string());
2034 }
2035
2036 #[test]
2037 fn a_retention_change_for_a_closing_tenant_is_refused() {
2038 let f = fixture();
2039 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2040 f.tenants.drain(id).expect("drains");
2041 assert_eq!(
2042 f.tenants
2043 .set_retention(Actor::Cli, id, Some(Retention::Years(1))),
2044 Err(TenantError::Closing)
2045 );
2046 assert_eq!(f.own_retention(id), None, "unchanged");
2047 assert_eq!(f.admin_records("tenant_retention")[0]["status"], 503);
2048 }
2049
2050 #[test]
2051 fn a_retention_change_whose_admin_record_fails_changes_nothing() {
2052 let f = fixture();
2053 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2054 f.conn()
2055 .execute_batch(
2056 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
2057 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
2058 )
2059 .expect("trigger");
2060 assert_eq!(
2061 f.tenants
2062 .set_retention(Actor::Cli, id, Some(Retention::Years(1))),
2063 Err(TenantError::AuditFailed)
2064 );
2065 assert_eq!(f.own_retention(id), None, "unchanged");
2066 let copies = exported(&f.tenant_catalog(id), None)
2067 .into_iter()
2068 .filter(|r| r["action"] == "retention")
2069 .count();
2070 assert_eq!(copies, 0, "no copy of a change that was not made");
2071 }
2072
2073 #[test]
2076 fn a_retention_change_whose_tenant_copy_fails_stands() {
2077 let f = fixture();
2078 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2079 f.tenant_catalog(id)
2080 .execute_batch(
2081 "CREATE TRIGGER dead BEFORE INSERT ON audit
2082 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
2083 )
2084 .expect("trigger");
2085 assert_eq!(
2086 f.tenants
2087 .set_retention(Actor::Cli, id, Some(Retention::Years(10))),
2088 Err(TenantError::AuditFailed)
2089 );
2090 assert_eq!(
2091 f.own_retention(id).as_deref(),
2092 Some("10y"),
2093 "the change stands"
2094 );
2095 assert_eq!(f.admin_records("tenant_retention")[0]["status"], 200);
2096 }
2097
2098 fn with_vcon(f: &Fixture) -> TenantId {
2100 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2101 let (found, body) = vcon(UUID, "first");
2102 f.tenants
2103 .store(id)
2104 .expect("its store")
2105 .create(&caller(), &found, &body)
2106 .expect("stored");
2107 id
2108 }
2109
2110 fn hold(f: &Fixture, id: TenantId) {
2111 f.tenant_catalog(id)
2112 .execute("UPDATE vcon SET hold = 1", [])
2113 .expect("a hold");
2114 }
2115
2116 fn until_closing(f: &Fixture, id: TenantId) {
2118 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
2119 while f.tenants.store(id).is_ok() {
2120 assert!(std::time::Instant::now() < deadline, "never closing");
2121 std::thread::yield_now();
2122 }
2123 }
2124
2125 #[test]
2126 fn a_tenant_already_closing_cannot_be_drained_again() {
2127 let f = fixture();
2128 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2129 f.tenants.drain(id).expect("drains");
2130 assert_eq!(f.tenants.drain(id).err(), Some(TenantError::Closing));
2131 }
2132
2133 #[test]
2134 fn removing_an_unknown_tenant_is_not_found() {
2135 let f = fixture();
2136 let id = TenantId::generate();
2137 assert_eq!(
2138 f.tenants.remove(Actor::Cli, id, Removal::Empty),
2139 Err(TenantError::NotFound)
2140 );
2141 assert_eq!(f.tenants.store(id).err(), Some(TenantError::NotFound));
2142 let records = f.admin_records("tenant_remove");
2143 assert_eq!(records.len(), 1);
2144 assert_eq!(records[0]["status"], 404);
2145 assert_eq!(records[0]["target"], id.to_string());
2146 }
2147
2148 #[test]
2149 fn an_empty_tenant_is_removed_with_its_key_directory_and_row() {
2150 let f = fixture();
2151 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2152 drop(f.tenants.store(id).expect("opened by a request"));
2153 f.clock.set(datetime!(2026-10-23 13:00 UTC));
2154 f.tenants
2155 .remove(Actor::Cli, id, Removal::Empty)
2156 .expect("removed");
2157 assert!(!f.has_row(id), "its row and wrapped key are gone");
2158 assert_eq!(f.tenants.store(id).err(), Some(TenantError::NotFound));
2159 assert_eq!(f.dirs(), Vec::<OsString>::new(), "its directory is gone");
2160 assert_eq!(f.tenants.list(Actor::Cli), Ok(Vec::new()));
2161 let records = f.admin_records("tenant_remove");
2162 assert_eq!(records.len(), 1);
2163 assert_eq!(records[0]["actor"], "cli");
2164 assert_eq!(records[0]["target"], id.to_string());
2165 assert_eq!(records[0]["status"], 204);
2166 assert_eq!(records[0]["at"], "2026-10-23T13:00:00Z");
2167 }
2168
2169 #[test]
2170 fn an_empty_removal_is_refused_while_any_version_is_kept() {
2171 let f = fixture();
2172 let id = with_vcon(&f);
2173 let uuid = vcon(UUID, "first").0.uuid;
2174 f.tenants
2175 .store(id)
2176 .expect("its store")
2177 .delete(&caller(), uuid, Retention::Years(7))
2178 .expect("deleted, so only a hidden version is kept");
2179 assert_eq!(
2180 f.tenants.remove(Actor::Cli, id, Removal::Empty),
2181 Err(TenantError::HoldsVcons)
2182 );
2183 assert!(f.has_row(id));
2184 let store = f.tenants.store(id).expect("still served");
2185 assert_eq!(store.counts().map(|c| c.versions), Ok(1));
2186 assert_eq!(f.admin_records("tenant_remove")[0]["status"], 409);
2187 }
2188
2189 #[test]
2190 fn a_forced_removal_takes_the_vcons_too() {
2191 let f = fixture();
2192 let id = with_vcon(&f);
2193 f.tenants
2194 .remove(Actor::Cli, id, Removal::Force)
2195 .expect("removed");
2196 assert!(!f.has_row(id));
2197 assert_eq!(f.dirs(), Vec::<OsString>::new());
2198 }
2199
2200 #[test]
2201 fn a_forced_removal_is_refused_while_a_uuid_is_held() {
2202 let f = fixture();
2203 let id = with_vcon(&f);
2204 hold(&f, id);
2205 assert_eq!(
2206 f.tenants.remove(Actor::Cli, id, Removal::Force),
2207 Err(TenantError::UnderHold)
2208 );
2209 assert!(f.has_row(id));
2210 assert!(f.tenants.store(id).is_ok(), "still served");
2211 assert_eq!(f.admin_records("tenant_remove")[0]["status"], 409);
2212 }
2213
2214 #[test]
2215 fn overriding_holds_removes_a_held_tenant() {
2216 let f = fixture();
2217 let id = with_vcon(&f);
2218 hold(&f, id);
2219 f.tenants
2220 .remove(Actor::Cli, id, Removal::ForceOverridingHolds)
2221 .expect("removed");
2222 assert!(!f.has_row(id));
2223 assert_eq!(f.dirs(), Vec::<OsString>::new());
2224 }
2225
2226 #[test]
2230 fn a_write_in_flight_during_an_empty_removal_is_kept() {
2231 let f = fixture();
2232 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2233 let (found, body) = vcon(UUID, "first");
2234 let held = f.tenants.store(id).expect("a request in flight");
2235 let tenants = &f.tenants;
2236 let fx = &f;
2237 let (found_ref, body_ref) = (&found, &body);
2238 let result = std::thread::scope(move |s| {
2239 let (done, removed) = std::sync::mpsc::channel();
2240 s.spawn(move || {
2241 done.send(tenants.remove(Actor::Cli, id, Removal::Empty))
2242 .expect("send");
2243 });
2244 until_closing(fx, id);
2245 assert!(
2246 removed
2247 .recv_timeout(std::time::Duration::from_millis(100))
2248 .is_err(),
2249 "the removal waits for the write"
2250 );
2251 held.create(&caller(), found_ref, body_ref)
2252 .expect("the write is acknowledged");
2253 drop(held);
2254 removed.recv().expect("the removal ends")
2255 });
2256 assert_eq!(result, Err(TenantError::HoldsVcons));
2257 assert_eq!(
2258 f.tenants
2259 .store(id)
2260 .expect("still served")
2261 .get(&caller(), found.uuid)
2262 .expect("the write is kept")
2263 .body,
2264 body
2265 );
2266 }
2267
2268 #[test]
2269 fn a_second_removal_while_one_drains_is_refused() {
2270 let f = fixture();
2271 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2272 let held = f.tenants.store(id).expect("a request in flight");
2273 let tenants = &f.tenants;
2274 let fx = &f;
2275 std::thread::scope(move |s| {
2276 let first = s.spawn(move || tenants.remove(Actor::Cli, id, Removal::Force));
2277 until_closing(fx, id);
2278 assert_eq!(
2279 fx.tenants.remove(Actor::Cli, id, Removal::Empty),
2280 Err(TenantError::Closing)
2281 );
2282 drop(held);
2283 assert_eq!(first.join().expect("the first returns"), Ok(()));
2284 });
2285 let statuses: Vec<Value> = f
2286 .admin_records("tenant_remove")
2287 .into_iter()
2288 .map(|r| r["status"].clone())
2289 .collect();
2290 assert_eq!(statuses, [503, 204]);
2291 }
2292
2293 #[test]
2294 fn a_removal_whose_record_fails_removes_nothing() {
2295 let f = fixture();
2296 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2297 f.conn()
2298 .execute_batch(
2299 "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
2300 BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
2301 )
2302 .expect("trigger");
2303 assert_eq!(
2304 f.tenants.remove(Actor::Cli, id, Removal::Empty),
2305 Err(TenantError::AuditFailed)
2306 );
2307 assert!(f.has_row(id));
2308 assert_eq!(f.dirs().len(), 1);
2309 assert!(f.tenants.store(id).is_ok(), "served again");
2310 }
2311
2312 #[test]
2315 fn a_removal_cut_off_after_its_commit_finishes_at_the_next_start() {
2316 let mut f = fixture();
2317 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2318 drop(f.tenants.store(id).expect("opened by a request"));
2319 f.fs.fail_from(f.fs.log().len());
2320 assert_eq!(f.tenants.remove(Actor::Cli, id, Removal::Empty), Ok(()));
2321 assert_eq!(f.dirs().len(), 1, "the dead process removed nothing");
2322 f.restart();
2323 assert!(!f.has_row(id));
2324 assert_eq!(f.dirs(), Vec::<OsString>::new());
2325 }
2326
2327 #[test]
2328 fn removal_deletes_the_directory_on_disk() {
2329 let dir = tempfile::tempdir().expect("temp dir");
2330 let tenants = Tenants::open(
2331 dir.path(),
2332 Master::new().key(),
2333 Arc::new(crate::fs::OsFs),
2334 Arc::new(ManualClock::at(datetime!(2026-10-23 12:00 UTC))),
2335 )
2336 .expect("opens");
2337 let id = tenants.create(Actor::Cli, "acme", None).expect("made");
2338 let tenant_dir = dir.path().join("tenants").join(id.to_string());
2339 assert!(tenant_dir.join("catalog.sqlite").is_file());
2340 tenants
2341 .remove(Actor::Cli, id, Removal::Empty)
2342 .expect("removed");
2343 assert!(!tenant_dir.exists());
2344 }
2345
2346 #[test]
2347 fn two_tenants_keep_the_same_uuid_apart() {
2348 let f = fixture();
2349 let a = f.tenants.create(Actor::Cli, "a", None).expect("made");
2350 let b = f.tenants.create(Actor::Cli, "b", None).expect("made");
2351 let (found, kept_by_a) = vcon(UUID, "kept by a");
2352 let (_, kept_by_b) = vcon(UUID, "kept by b");
2353 let (store_a, store_b) = (
2354 f.tenants.store(a).expect("a's store"),
2355 f.tenants.store(b).expect("b's store"),
2356 );
2357 assert!(matches!(
2358 store_a.create(&caller(), &found, &kept_by_a),
2359 Ok(Created::New(_))
2360 ));
2361 assert!(matches!(
2362 store_b.create(&caller(), &found, &kept_by_b),
2363 Ok(Created::New(_))
2364 ));
2365 let read = |store: &Store| store.get(&caller(), found.uuid).map(|s| s.body);
2366 assert_eq!(read(&store_a), Ok(kept_by_a));
2367 assert_eq!(read(&store_b), Ok(kept_by_b.clone()));
2368 store_a
2369 .delete(&caller(), found.uuid, Retention::Years(7))
2370 .expect("a deletes its own");
2371 assert_eq!(read(&store_a), Err(StoreError::Gone));
2372 assert_eq!(read(&store_b), Ok(kept_by_b), "b's is untouched");
2373 }
2374
2375 #[test]
2378 fn a_tenants_data_keys_are_bound_to_its_own_id() {
2379 let f = fixture();
2380 let id = f.tenants.create(Actor::Cli, "acme", None).expect("made");
2381 let (found, body) = vcon(UUID, "first");
2382 let Ok(Created::New(version)) =
2383 f.tenants
2384 .store(id)
2385 .expect("its store")
2386 .create(&caller(), &found, &body)
2387 else {
2388 panic!("a new vCon")
2389 };
2390 let wrapped: Vec<u8> = f
2391 .tenant_catalog(id)
2392 .query_row(
2393 "SELECT wrapped_key FROM version WHERE version = ?1",
2394 [version.0],
2395 |r| r.get(0),
2396 )
2397 .expect("its row");
2398 let ctx = Context::Version {
2399 tenant: id,
2400 uuid: found.uuid,
2401 version,
2402 };
2403 assert!(envelope::unwrap(&f.tenant_key(id), &ctx, &wrapped).is_ok());
2404 }
2405
2406 fn answers(store: &Store) -> Vec<String> {
2408 let (found, body) = vcon(UUID, "b's own");
2409 let (_, newer) = vcon(UUID, "b's newer");
2410 let kind = |r: Result<Created, StoreError>| match r {
2411 Ok(Created::New(_)) => "new".to_owned(),
2412 Ok(Created::Same(_)) => "same".to_owned(),
2413 Err(e) => format!("{e:?}"),
2414 };
2415 let who = caller();
2416 let mut answers = vec![
2417 format!("{:?}", store.get(&who, found.uuid).map(|_| ())),
2418 format!("{:?}", store.delete(&who, found.uuid, Retention::Years(7))),
2419 kind(store.create(&who, &found, &body)),
2420 kind(store.create(&who, &found, &body)),
2421 ];
2422 let current = store.get(&who, found.uuid).expect("stored").version;
2423 answers.push(format!(
2424 "{:?}",
2425 store.replace(&who, current, &found, &newer).map(|_| ())
2426 ));
2427 answers.push(format!(
2428 "{:?}",
2429 store.delete(&who, found.uuid, Retention::Years(7))
2430 ));
2431 answers.push(format!("{:?}", store.get(&who, found.uuid).map(|_| ())));
2432 answers
2433 }
2434
2435 #[test]
2436 fn nothing_one_tenant_does_shows_whether_another_holds_a_uuid() {
2437 let alone = fixture();
2438 let b = alone.tenants.create(Actor::Cli, "b", None).expect("made");
2439 let expected = answers(&alone.tenants.store(b).expect("b's store"));
2440
2441 let f = fixture();
2442 let a = f.tenants.create(Actor::Cli, "a", None).expect("made");
2443 let b = f.tenants.create(Actor::Cli, "b", None).expect("made");
2444 let store_a = f.tenants.store(a).expect("a's store");
2445 let (found, body) = vcon(UUID, "a's");
2446 store_a
2447 .create(&caller(), &found, &body)
2448 .expect("a holds it");
2449 let (other, body) = vcon("01a07da8-c2bb-83e5-b9a2-279e0d16bc47", "a's other");
2450 store_a.create(&caller(), &other, &body).expect("stored");
2451 store_a
2452 .delete(&caller(), other.uuid, Retention::Years(7))
2453 .expect("and has deleted another");
2454 assert_eq!(answers(&f.tenants.store(b).expect("b's store")), expected);
2455 assert_eq!(
2456 expected,
2457 [
2458 "Err(NotFound)",
2459 "Err(NotFound)",
2460 "new",
2461 "same",
2462 "Ok(())",
2463 "Ok(())",
2464 "Err(Gone)"
2465 ]
2466 );
2467 }
2468}