Commit 896d6b5f authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

fix(fm): keep bootstrapping federations after one partner fails

establish_missing() is a startup path: one unreachable or
half-configured partner aborted FM boot entirely. Skip the failing
partner and continue with the rest.
parent 933be749
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ from federation_manager.contracts.ewbi import (
)
from federation_manager.domain.errors import (
    FederationAlreadyExists,
    FederationError,
    FederationEstablishmentFailed,
    PartnerNotActive,
    PartnerResponseInvalid,
@@ -104,8 +105,13 @@ class FederationEstablishmentService:
    async def establish_missing(self) -> list[FederationContext]:
        established: list[FederationContext] = []
        for partner in await self._partner_repo.list_active():
            if await self._contexts.find_active_outbound(partner.id) is None:
            if await self._contexts.find_active_outbound(partner.id) is not None:
                continue
            try:
                established.append(await self.establish(partner))
            except FederationError:
                # One unreachable or half-configured partner must not stop FM from starting.
                continue
        return established


+24 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ from federation_manager.application.federation import (
from federation_manager.domain.errors import (
    FederationEstablishmentFailed,
    PartnerNotActive,
    PartnerRequestFailed,
    PartnerResponseInvalid,
)
from federation_manager.domain.models import EwbiResponse, FederationContext, PartnerOP
@@ -159,3 +160,26 @@ async def test_bootstrap_federates_only_active_partners_without_a_context() -> N

    assert [c.partner_op_id for c in established] == [fresh.id]
    assert [call[0] for call in ewbi.calls] == [fresh.id]


async def test_bootstrap_skips_partners_that_fail_and_keeps_going() -> None:
    broken = _partner()
    healthy = _partner()

    class FailingForOne(FakeEwbiClient):
        async def post(
            self, partner: PartnerOP, path: str, payload: dict[str, object]
        ) -> EwbiResponse:
            if partner.id == broken.id:
                raise PartnerRequestFailed(partner.id)
            return await super().post(partner, path, payload)

    service, _, contexts = _service(
        [broken, healthy], ewbi=FailingForOne(EwbiResponse(200, _accepted()))
    )

    established = await service.establish_missing()

    assert [c.partner_op_id for c in established] == [healthy.id]
    assert await contexts.find_active_outbound(broken.id) is None
    assert await contexts.find_active_outbound(healthy.id) is not None