using Deal.Api; using Deal.Api.Dtos; using Deal.Api.Services; using Deal.Tests.Unit.Support; namespace Deal.Tests.Unit.Api; /// /// Тесты PipelinePumpGate — общий воркер-гейт pump тенанта (план Task 11, Ruling 8; аналог /// asyncio.Lock pipeline.py L38–42): admin/tick и фоновый цикл не разбирают очередь одного тенанта /// одновременно, разные тенанты не связаны. /// /// /// Семантика — как прототип pump_once L901–902: занятый pump тенанта → повторный вход (TryEnter) даёт /// false, вызывающий пропускает проход (очередь дождётся следующего срабатывания). Exit освобождает /// гейт; гейт потокобезопасен (ConcurrentDictionary + атомарный TryAdd), параллельные входы одного /// тенанта выигрывает ровно один. /// public sealed class PipelinePumpGateTests { // Тенант A теста. private static readonly Guid TenantA = Guid.NewGuid(); // Тенант B теста. private static readonly Guid TenantB = Guid.NewGuid(); [Fact] public void TryEnter_FirstEnterWinsSecondForSameTenantIsRejected() { var gate = new PipelinePumpGate(); Assert.True(gate.TryEnter(TenantA)); // первый воркер (admin/tick или фоновый цикл) вошёл Assert.False(gate.TryEnter(TenantA)); // второй для того же тенанта пропускает проход (L901–902) } [Fact] public void Exit_ReleasesGateForSameTenant() { var gate = new PipelinePumpGate(); Assert.True(gate.TryEnter(TenantA)); gate.Exit(TenantA); Assert.True(gate.TryEnter(TenantA)); // после finally-освобождения гейт снова свободен } [Fact] public void TryEnter_DifferentTenantsEnterIndependently() { var gate = new PipelinePumpGate(); Assert.True(gate.TryEnter(TenantA)); Assert.True(gate.TryEnter(TenantB)); // pump разных тенантов не конкурирует (свои схемы/очереди) gate.Exit(TenantA); Assert.True(gate.TryEnter(TenantA)); // освобождение A не влияет на занятый B Assert.False(gate.TryEnter(TenantB)); } [Fact] public void Exit_WithoutEnterDoesNotBreakLaterEntry() { var gate = new PipelinePumpGate(); // Exit без парного TryEnter не роняет состояние (нет «висящего» флага после сбоя до входа). gate.Exit(TenantA); Assert.True(gate.TryEnter(TenantA)); } }