Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ publish = true
readme = "README.md"
repository = "https://github.com/torrust/torrust-server-lib"
rust-version = "1.88"
version = "0.3.0"
version = "0.4.0"

[dependencies]
derive_more = { version = "2", features = [ "as_ref", "constructor", "display", "from" ] }
Expand Down
89 changes: 89 additions & 0 deletions src/registar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,24 @@ impl<M> Registar<M> {
Ok(())
}

/// Removes the registration for a final binding and returns it.
///
/// Returns `None` when no service is registered with that binding. A
/// service that stops should remove its own registration, so the same
/// binding can be registered again, for example when a stopped test
/// environment restarts on a fixed port.
///
/// The registry is keyed by binding only, so this removes whichever
/// registration holds that binding. Callers should pass only a binding
/// they registered themselves.
pub async fn deregister(&self, service_binding: &ServiceBinding) -> Option<RegisteredService<M>> {
self.registry
.lock()
.await
.remove(service_binding)
.map(|registration| RegisteredService { registration })
}

/// Returns a deterministic, side-effect-free snapshot of all services.
///
/// Results are ordered by protocol, then final socket address, never by
Expand Down Expand Up @@ -325,4 +343,75 @@ mod tests {

assert_eq!(error, RegistrationError::DuplicateBinding(service_binding));
}

#[tokio::test]
async fn it_should_remove_a_registration_and_return_it_when_deregistered() {
let registar = Registar::default();
let service_binding = binding(Protocol::HTTP, 8000);

registar
.give_form()
.register(ServiceRegistration::new(service_binding.clone(), "first", None))
.await
.expect("registration should succeed");

let removed = registar.deregister(&service_binding).await;

assert_eq!(removed.map(|service| *service.metadata()), Some("first"));
assert!(registar.services().await.is_empty());
}

#[tokio::test]
async fn it_should_allow_registering_a_binding_again_after_it_was_deregistered() {
let registar = Registar::default();
let service_binding = binding(Protocol::HTTP, 8000);

registar
.give_form()
.register(ServiceRegistration::new(service_binding.clone(), "first", None))
.await
.expect("initial registration should succeed");
registar.deregister(&service_binding).await;

registar
.give_form()
.register(ServiceRegistration::new(service_binding, "second", None))
.await
.expect("registration after deregistration should succeed");

assert_eq!(registar.services().await[0].metadata(), &"second");
}

#[tokio::test]
async fn it_should_return_none_when_the_binding_to_deregister_is_not_registered() {
let registar = Registar::<()>::default();

let removed = registar.deregister(&binding(Protocol::HTTP, 8000)).await;

assert!(removed.is_none());
}

#[tokio::test]
async fn it_should_keep_the_other_registrations_when_one_is_deregistered() {
let registar = Registar::default();
let removed_binding = binding(Protocol::HTTP, 8000);

for (port, metadata) in [(8000, "removed"), (9000, "kept")] {
registar
.give_form()
.register(ServiceRegistration::new(binding(Protocol::HTTP, port), metadata, None))
.await
.expect("registration should succeed");
}

registar.deregister(&removed_binding).await;

let metadata: Vec<_> = registar
.services()
.await
.into_iter()
.map(|service| *service.metadata())
.collect();
assert_eq!(metadata, ["kept"]);
}
}
Loading