📋 Async IBD ingestion — przegląd kodu
0 / 136 English Przewodnik UX Engineering
Wholesale · Deliveries · pull request

Async inbound-delivery ingestion — przewodnik przeglądu

Uporządkowany, prowadzony przegląd każdego pliku .cs w tej zmianie względem main — co sprawdzić w każdym pliku i jaki ma poziom ryzyka — aby przegląd dało się podzielić w zespole i nic nie umknęło.

136 plików .cs121 nowych · 15 zmienionych18 etapów przeglądu 42 wysokie47 średnie46 niskie
Zakres. Właściwy PR — gałąź względem main: 13 commitów, 136 plików .cs (121 nowych, 15 zmienionych). Przeglądaj od góry do dołu; każdy etap opiera się na poprzednim.

Jak korzystać

Przeglądaj od góry do dołu — model → porty → orkiestracja → obrzeża → testy. Odhacz plik po sprawdzeniu; postęp zapisuje się w przeglądarce.

Podział pracy

Przydziel każdej osobie jeden lub więcej etapów. Etapy wysokiego ryzyka (7–10: bazowy handler, handlery kroków, komendy sterujące) zacznij od najmocniejszych recenzentów.

Co znaczą „checks”

Każdy plik wymienia konkretne rzeczy do zweryfikowania — warunki strażników, transakcje, współbieżność, idempotencję, klasyfikację błędów — to nie streszczenie. Czytaj kod; traktuj to jako soczewkę.

1

Domena — model

7 plików2 wysokie

Na co zwrócić uwagę: Agregat pozyskiwania, obiekty wartości status/krok oraz zdarzenie w kolejce. Przeczytaj to najpierw — każdy handler opiera się na tej maszynie stanów.

src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Events/InboundDeliveryIngestionQueuedEvent.cswysokie
Zdarzenie domeny wypalane przy Create, Start i RestartFromBeginning; przenosi numer próby, aby wyposażyć zadania w tle strażnikiem idempotencji (przetwórz tylko, jeśli próba zadania odpowiada próbie agregatu).
  • Linie 8–10: Zdarzenie przechwytuje Attempt w momencie publikacji; weryfikuj, że zdarzenie jest publikowane PO UpdateField podniesienia AttemptNumber (linia 253, następnie 259 w RestartFromBeginning) — jeśli kolejność jest błędna, zadania widzą przestarzałą próbę i ignorują pracę
  • Linia 16: Pole Attempt jest publiczne set; upewnij się, że deserializacja lub późna mutacja tego pola nie korumpuje deduplicacji zadań
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/InboundDeliveryIngestion.cswysokie
Organizuje długotrwały asynchroniczny potok pozyskiwania: śledzi status/krok/próbę, utrzymuje okno okresu karencji z immunitetem opartym na tokenach przed przestarzałymi zadaniami, audytuje każdy krok i koordynuje przejścia między stanami Running/Grace/Stopped/Succeeded.
  • Linie 173–188 (MarkTerminallyFailed): Weryfikuj, że warunek osłony StatusCode obejmuje wszystkie stany nieterminalnej awarii; uważaj, aby wyścig między handler kroku w locie + watchdog awarii terminalnej nie podwoił rekordu kroku dla bieżącej próby
  • Linia 341 (ReachedGracePhase): Potwierdź, że HasStepSucceeded(AwaitGracePeriodCode) jest poprawną heurystyką — jeśli AwaitGracePeriod można pominąć lub jeśli jego wiersz może być brakujący, to łamie wznowienie grace-pause
  • Linia 253 (RestartFromBeginning): Marker jest logowany przy starym AttemptNumber przed wzniesieniem; weryfikuj, że stare zadania w locie widzą starą próbę na zdarzeniu linii 259 i poprawnie nie robią nic poprzez strażnika próby
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/IngestionOrigin.csniskie
Lekki obiekt wartości źródła przesłania (WholeSale/PartnerApp/Acquisition); używany do śladu audytu i filtrowania.
  • Linia 24: Single() na FromCode; weryfikuj brak nieużywanych wartości kodu i że deserializacja starych rekordów nie dryfuje
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/IngestionStatus.csśrednie
Obiekt wartości definiujący siedem stanów cyklu życia pozyskiwania (Queued/Running/Failed/Succeeded/IboGenerated/Stopped/InGracePeriod) i ich stałe; tablica InProgressCodes steruje, które statusy interfejs użytkownika i osłony traktują jako 'aktywne'.
  • Linie 52–53: Weryfikuj, że InProgressCodes zawiera wszystkie stany, w których użytkownik może wciąż działać (Stopped, InGracePeriod, IboGenerated wszystkie obecne?); sprawdź, że osłony takie jak EnsureCanBeStopped używają tej tablicy prawidłowo lub mają równoważną logikę
  • FromCode linia 55: Single() wyrzuci, jeśli kod statusu jest sierota (np. deserializowany ze starego wiersza DB); weryfikuj, że migracja chroni przed ponownym użyciem lub usunięciem wartości kodu
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/IngestionStepExecution.csśrednie
Encja audytu append-only immutable dla każdego wykonania kroku; wspiera kontrole idempotencji (HasStepCompleted) i przechwytywanie błędów (ładunek JSON).
  • Linia 55 (FinishedAtUtc = DateTime.UtcNow): Weryfikuj, że pochylenie zegara lub podróż w czasie nie pozwala FinishedAtUtc < StartedAtUtc (krytyczne dla integralności śladu audytu)
  • Linie 46 i 56: errorPayload jest przechowywany ale nigdy nie walidowany; potwierdź, że kształt JSON (Dict<Guid, Dict<string, List<string>>> vs mały obiekt {message}) jest wymuszany/udokumentowany w handlerach i interfejs użytkownika deserializuje bezpiecznie
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/IngestionStepStatus.csniskie
Atomowy obiekt wartości dla statusu wykonania kroku (Pending/Running/Succeeded/Failed/Skipped); duplikuje status agregatu ale na krok.
  • Linia 33: Single() wyrzuci, jeśli wiersz kroku ma przestarzały/nieznany StatusCode; weryfikuj, że konfiguracja EF zapewnia, że tylko prawidłowe kody są utrzymywane
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/IngestionStepType.csśrednie
Definiuje 10 wykonalnych kroków potoku + 3 kody markerów append-only; FromCode i kolekcje Pipeline/Markers wspierają sekwencjonowanie kroków i kontrole idempotencji.
  • Linie 74–77: Weryfikuj, że kolejność Pipeline odpowiada spodziewanej sekwencji wykonania choreografii (ParseRawPayload → ... → CreateInboundDelivery); wszystkie kody spoza kolejności będą bezgłośnie pozwalać na duplikowane wykonanie
  • Linie 85–86: Single() na Concat Pipeline+Markers; jeśli kod jest na obu listach lub na żadnej z nich, bezgłośnie się nie powiedzie — audytuj alokację kodu, aby uniknąć kolizji
2

Domena — reguły biznesowe

5 plików1 wysokie

Na co zwrócić uwagę: Strażniki przejść stop / start / resume / restart / delete. Sprawdź, czy każdy koduje prawidłowe dozwolone stany i jest ewaluowany przed jakąkolwiek mutacją.

src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Rules/IngestionCanBeDeletedOnlyWhenInactiveRule.csśrednie
Strażnik operacji usuwania: dozwolone są tylko statusy Failed lub Stopped; zapobiega osieroceniu aktywnych zadań w tle.
  • Sprawdź, czy reguła prawidłowo odrzuca Queued/Running (aktywne zadania wciąż referencjonują ten wiersz; usunięcie grozi błędami 'zadanie nie znalezione' lub osieroconym pracami) i Succeeded (ścieżka audytu rzeczywistej dostawy)
  • Potwierdź stan InGracePeriodCode: jeśli pozyskiwanie jest wstrzymane w oknie grace, czy status to Stopped czy InGracePeriod? Jeśli to osobny status, reguła może wymagać jego dopuszczenia
  • Sprawdź, czy obiekty wywołujące wymuszają najpierw stop na każdym pozyskiwaniu Queued/Running w UI/kontrolerze, zapobiegając zamieszaniu, gdy delete jest odrzucony
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Rules/IngestionCanBeRestartedUnlessCompletedRule.cswysokie
Strażnik operacji restart: blokuje Succeeded i IboGenerated (dostawa już utworzona), dozwala Queued/Running/Failed/Stopped.
  • Sprawdź, czy konstanta IboGenerated jest zdefiniowana i udokumentowana jako 'dostawa utworzona, oczekiwanie na wygenerowane zamówienie'; potwierdź, że jest to ostateczny stan przed terminalnym
  • Potwierdź, że numer próby jest inkrementowany atomowo w tej samej transakcji, która sprawdza tę regułę, zapewniając, że nieświeże zadania w tle widzą nową próbę i stają się no-ops
  • Upewnij się, że token optymistycznej współbieżności jest przechwytywany przed rozpoczęciem restart; każde zadanie converge w locie ze starym tokenem nie powiedzie się na SaveAsync() po zatwierdzeniu restart
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Rules/IngestionCanBeResumedOnlyFromGracePauseRule.csśrednie
Strażnik operacji resume (re-arm okna grace): status musi być Stopped I flaga reachedGracePhase musi być true.
  • Krytyczne: `IsBroken()` używa OR (||) do kombinacji dwóch warunków — sprawdź operator precedence: `statusCode is not StoppedCode || !reachedGracePhase` odczytuje się jako `(status ≠ Stopped) OR (grace nie osiągnięty)`, logika poprawna
  • Potwierdź, że reachedGracePhase śledzi, czy krok UploadKeys został ukończony (oznaczając wejście w okres grace); ta flaga musi być niezmienna lub ustalona tylko raz
  • Sprawdź, czy obiekty wywołujące zawsze przekazują wartość flagi na żywo z agregatu, a nie nieświeżą kopię; waściwość z concurrent Start/Restart, która czyści flagę, mogłaby ominąć regułę
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Rules/IngestionCanBeStartedOnlyWhenStoppedOrFailedRule.csniskie
Strażnik operacji start (resume-from-checkpoint): dozwolone są tylko statusy Stopped lub Failed.
  • Sprawdź, czy reguła blokuje Queued/Running (już w locie, start no-op odrzucony) i Succeeded (terminalny, nieodwracalny)
  • Potwierdź, że konstały StoppedCode i FailedCode istnieją i są wzajemnie odrębne od QueuedCode/RunningCode
  • Upewnij się, że obiekt wywołujący sprawdza tę regułę PRZED próbą przywrócenia artefaktów lub inkrementacji liczników punktów kontrolnych
src/WOCK.WholeSale.Domain/Delivery/InboundDeliveryIngestion/Rules/IngestionCanBeStoppedOnlyWhileActiveRule.csniskie
Strażnik operacji stop/pause: dozwolone są tylko statusy Queued, Running lub InGracePeriod.
  • Sprawdź, czy logika `IsBroken()` jest prawidłowa: powinna zwrócić true wtedy i tylko wtedy, gdy status NIE jest jednym z trzech dozwolonych stanów (podwójna negacja jest tutaj prawidłowa)
  • Potwierdź, że konstanta InGracePeriodCode jest zdefiniowana i pasuje do enumeracji statusu używanej w innym miejscu w domenie pozyskiwania
  • Sprawdź, czy obiekty wywołujące wywoływają tę regułę na początku procedury stop/pause (przed jakimikolwiek mutacjami), a nie po częściowych zmianach stanu
3

Udostępnione zmiany w dostarczeniu i kluczach

4 plików1 wysokie

Na co zwrócić uwagę: Zmiany w istniejącym InboundDelivery / Key, aby operacja konwersji mogła utworzyć dostarczenie z już przesłanymi kluczami. Przejrzyj tylko te fragmenty — bramkę pomijania przesyłania kluczy i wstępnie wyliczane nazwy obiektów blob.

src/WOCK.WholeSale.Application/Deliveries/InboundDelivery/EventHandlers/InboundDeliveryCreated/UploadKeysToStorageEventHandler.csśrednie
Wewnętrzny obsługiwacz zdarzeń do synchronicznego przesyłania obiektów blob kluczy — bramka na linii 18 zapobiega podwójnemu przesłaniu gdy asynchroniczny ingestion przesyła wcześniej.
  • Sprawdź wczesne zwrócenie na linii 18–22 gdy SkipKeyUpload=true — zapewnia, że obsługiwacz wychodzi przed wejściem do iteracji AllFileKeys (linia 36) i pętli przesyłania wsadowego (linie 33–64), zapobiegając ponownym przesłaniom już utrwalonych obiektów blob
  • Sprawdź rozmiar partii przesyłania wsadowego (linia 34 = 5000) i rozdzielczość BlobFileName (linia 43) — używa Key.BlobFileName który teraz wywołuje BuildBlobFileName, więc nazewnictwo odpowiada wstępnie wyliczonej kalkulacji asynchronicznego pipeline'u
  • Potwierdź, że walidacja Checksum (linie 46–49) rzuca wyjątek jeśli przesłany checksum ≠ key.Checksum — bezpieczne ponieważ ścieżka asynchroniczna wstępnie przypisuje zarówno Id jak i Checksum, więc nigdy się nie uaktywni gdy SkipKeyUpload=true
src/WOCK.WholeSale.Domain/Delivery/InboundDelivery/Events/InboundDeliveryCreated/InboundDeliveryCreatedEvent.csniskie
Okablowanie fabryki zdarzeń dla flagi skipKeyUpload — prowadzi ją przez zdarzenie najwyższego poziomu do InternalEvent, aby obsługiwacz mógł warunkowo pominąć przesłanie obiektów blob.
  • Sprawdź, że parametr skipKeyUpload propaguje się z InboundDeliveryCreatedEvent do InboundDeliveryCreatedInternalEvent (linia 12) — ścieżka false (domyślna) aktywuje normalne przesyłanie, ścieżka true (asynchroniczny ingestion) je pomija
  • Sprawdź, że zdarzenie ustawia domyślnie skipKeyUpload=false (linia 10), aby wszystkie istniejące wywołania pozostały bezpieczne bez zmian kodu (wsteczna kompatybilność dla przepływów bez ingestion)
  • Potwierdź, że pobieracz właściwości InboundDeliveryCreatedInternalEvent (linia 37) jest publiczny, aby obsługiwacz mógł uzyskać dostęp; komentarz wyjaśnia terminologię 'out-of-band upload'
src/WOCK.WholeSale.Domain/Delivery/InboundDelivery/InboundDelivery.csśrednie
Fabryka tworzenia agregatu dla ścieżki asynchronicznego ingestion z warunkowym przesyłaniem kluczy — dodaje parametr skipKeyUpload, aby wyłączyć przesyłanie obiektów blob w procesie gdy klucze są wcześniej przesyłane.
  • Sprawdź, że skipKeyUpload propaguje się do konstruktora InboundDeliveryCreatedEvent (linia 163) — steruje bramką podwójnego przesyłania na poziomie obsługiwacza zdarzeń
  • Potwierdź, że gracePeriod=0 w operacji konwersji (linia 108 CreateInboundDeliveryStepHandler) steruje opóźnieniem fazy grace dla wygenerowanych zamówień; bez ryzyka statusu/osi czasu ponieważ typ zdarzenia to Delayed, nie Internal
  • Sprawdź, że nie dochodzi do żadnej mutacji stanu kluczy podczas Create — wszystkie właściwości kluczy (Checksum, Id) są wstępnie przypisane przez asynchroniczny pipeline ingestion, więc walidacja Checksum w UploadKeysToStorageEventHandler linia 46 zawsze przechodzi gdy klucze istnieją
src/WOCK.WholeSale.Application/Deliveries/InboundDelivery/EventHandlers/InboundDeliveryCreated/SupplyInboundOrdersEventHandler.csśrednie
Współdzielony handler IBD-created, który linkuje/generuje Inbound Order z nadwyżki; teraz dodatkowo zapisuje wygenerowane IBO na scoped sinku dla converge ingestii.
  • Zweryfikuj, że sink.Record jest addytywne — NIE może zmieniać zachowania ścieżek sync / acquisition / resell (które nie czytają sinka).
  • Potwierdź, że IBO powstaje w tej samej transakcji co dostawa (SaveChangesAsync na kontekście Orders).
src/WOCK.WholeSale.Domain/Delivery/Key.cswysokie
Wyekstrahowana fabryka statyczna dla nazewnictwa obiektów blob + nowe przeciążenie CreateFileKey dla asynchronicznego ingestion, aby naprawić KeyId przed krokiem przesyłania.
  • Sprawdź, że BuildBlobFileName jest wywoływany przez właściwość instancji (linia 51), aby zapewnić jedyne źródło prawdy dla nazewnictwa {Id}.{ext} — krytyczne ponieważ asynchroniczny pipeline buduje nazwy obiektów blob z artefaktu przygotowanych kluczy (przed utworzeniem wiersza bazy danych) i muszą one odpowiadać temu co aplikacja tworzy po wstawieniu
  • Sprawdź, że CreateFileKey(File, Guid, string) zawsze ustawia IsNewest=true (linia 162) — słuszne dla nowych kluczy; logika ponownej sprzedaży ustawia OriginKeyId osobno, więc IsNewest pozostaje true w kopii 'nowego' klucza, false w starym kluczu (wg dokumentacji linie 95–97)
  • Potwierdź, że parametr Checksum w przeciążeniu akceptuje wstępnie wyliczoną wartość, aby pominąć ponowne obliczanie MD5 — krytyczne dla asynchronicznego pipeline'u który buduje nazwy obiektów blob przed trwałością bazy danych
4

Porty — abstrakcje i repozytorium

7 plików4 wysokie

Na co zwrócić uwagę: Interfejsy, od których zależy potok przetwarzania. Sprawdź umowy (i wymagane UserId w komendach działających w tle) przed ich implementacjami.

src/WOCK.Framework.Persistence.Abstraction/Repositories/IInboundDeliveryIngestionsRepository.cswysokie
Umowa repozytorium do ładowania agregatów pozysku z historią wykonania kroków, umożliwiająca obsługę kroków i zapytania o postęp.
  • GetByGeneratedInboundOrderIdAsync: zweryfikuj, że ograniczenie unikalności w tył (jeden pozysk na ID zamówienia) jest egzekwowane w schemacie/indeksach.
  • Potwierdź, że GetWithStepsAsync ładuje kolekcję Steps z chętnym pobraniem; brak .Include(s=>s.Steps) w implementacji powoduje utraciętych kroków w połowie potoku.
  • GetInProgressWithStepsAsync logika filtrowania: sprawdź, czy enum stanu obejmuje wszystkie stany nieterminalnego (Queued/Running/Failed/Stopped, ale nie Completed/Canceled).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IInboundDeliveryIngestionStorage.cswysokie
Abstrakcja I/O PVC dla surowych ciał multipart (wersjonowanych na każdą próbę), artefaktów JSON i czyszczenia plików tymczasowych.
  • Stream methods (OpenRawPayload, OpenLatestRawPayload) zwracają nieslaviane Streamy—zweryfikuj, że wołający je usuwają; przecieki strumieni blokują usuwanie plików.
  • SaveNextRawPayloadAsync: sekwencjonowanie wersji pod retry—potwierdź, że idempotentne ponowne zapisanie tego samego ciała zwraca ten sam klucz wersji, a nie inkrementowany.
  • PurgeOrphanTempFiles: logika cutoff musi wykluczać pliki aktualizowane podczas aktywnych pozysków (race na dotykaniu plików w trakcie analizy); potwierdź precyzję granularności sygnatury czasowej (problemy z precyzją na poziomie sekund).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IGeneratedInboundOrderSink.csniskie
Scoped, jednorazowe przekazanie IBO wygenerowanego z nadwyżkowych kluczy dostawy — pozwala converge'owi odczytać je synchronicznie zamiast komendy follow-up po commit.
  • Potwierdź rejestrację jako scoped (przez IService), żeby converge i wywołany przez niego SupplyInboundOrders dzieliły instancję i nic nie przeciekało między żądaniami.
  • Zweryfikuj, że czyta go tylko converge; pozostałe ścieżki Create go ignorują.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IInboundDeliveryMultipartParser.csśrednie
Analizuje zapisane surowe ciało multipart na CreateInboundDeliveryCommand; pisze wyekstrahowane pliki do PVC; odkłada ekspansję ZIP.
  • ParsedUploadResult LineZipPasswords indeksowane po indeksie—potwierdź, że indeksy są stabilne w całej przebudowie multipart (równoczesne ponawiania prób nie mogą reorderować linii).
  • Parser pisze pliki do PVC podczas analizy; jeśli parser nie powiedzie się po zapisie, pozostałe pliki tymczasowe wyciekną do czyszczenia przez cron. Zweryfikuj obsługę błędów czyszczących pliki lub oznaczających próbę do ręcznego przeglądu.
  • Stream disposal: potwierdź, że parser zamyka/usuwa strumień wejściowy po jego zużyciu (nie obowiązek wołającego).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IInboundDeliveryZipExpander.csśrednie
Rozpakuje archiwa ZIP do struktury artefaktu linii bazowej, dołączając ekstrahowane pliki jako odniesienia tymczasowe; obsługuje wpisy zaszyfrowane hasłami.
  • Expand(baseLine, zipTempFileName, password): mutuje baseLine in-place, a następnie usuwa plik archiwum. Jeśli mutacja nie powiedzie się w środku pętli, artefakt jest częściowo zmodyfikowany bez rollbacku; zweryfikuj, że wołający persystuje artefakt tylko po sukcesie.
  • WrongPassword flaga: potwierdź, że jest ustawiana tylko gdy deszyfrowanie się nie powiedzie (nie na uszkodzonym archiwum). UI musi odróżnić możliwe do naprawy przez użytkownika (ponów z hasłem) od niemożliwych do naprawy (uszkodzenie).
  • Temp file deletion: potwierdź idempotencję—jeśli Expand rzuci po usunięciu archiwum, ponowienie nie powinno się nie powieść na brakującym pliku.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IIngestionKeyReservationService.cswysokie
Ochrona TOCTOU: rezerwuje skróty kluczy w procesach podczas kroku GenerateKeys, zapobiegając równoczesnym pozyskom z duplikatami kluczy.
  • TryReserveAsync: "ponowne roszczenie skrótów, które już posiadł ten pozysk, to no-op"—potwierdź, że implementacja używa CAS lub atomowej operacji compare-and-set; optymistyczne blokowanie na wygaśnięciu TTL ryzykuje fałszywe konflikty.
  • TTL auto-expiry: jeśli TTL Redis zadziała między rezerwą i wydaniem, później Release() cicho powiedzie się na brakujących kluczach. Zweryfikuj, że myślący krok converge nie zakłada, że klucze są nadal zarezerwowane podczas pisania do DB.
  • Returned conflicts: potwierdź, czy zwrócony zestaw to: (a) wszystkie klucze aktualnie trzymane, (b) tylko klucze trzymane przez INNE posyski, czy (c) tylko klucze konfliktujące. Niejednoznaczny typ zwrotu łamie logikę ponowienia próby.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IIngestionProgressService.cswysokie
Hybrydowy model odczytu: push migawek postępu do Redis cache + indeks w toku; transmisja zmian stanu przez SignalR; zarządzanie flagą żądania zatrzymania.
  • PublishRunningAsync: syntetyczny rząd uruchomionego kroku nie persystowany do DB. Zweryfikuj, że agregaty załadowane z DB nie noszą zastałego stanu cache'u na "uruchomiona" pomiędzy granicami poleceń (cache vs DB skew na restart).
  • Stop-signal lifecycle: RequestStopAsync ustawia flagę, IsStopRequestedAsync ją sprawdza, ClearStopRequestAsync ją usuwa. Race: krok A sprawdza flagę (fałsz), użytkownik wywołuje RequestStop, krok A i tak się wykonuje—potwierdź, że flaga jest ponownie sprawdzana bezpośrednio przed operacją krytyczną.
  • RemoveAsync on delete: potwierdź, że transmisja SignalR używa poprawnego ID pozyskania i wszyscy klienci wypisują się z tego samego tematu (brak sierocych subskrypcji z retransmisji).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Abstractions/IKeysStorageMirror.csśrednie
Lustro załadowanych kluczy pliku na dedykowaną wolumin keys-storage PVC 1:1 z blob storage; idempotentne pod retry Hangfire.
  • IsConfigured: potwierdź, że no-op skip jest przezroczysty dla wołających (wyjątek vs cicha ścieżka sukcesu). Jeśli MirrorAsync jest wywoływana gdy IsConfigured=false, czy rzuca czy cicho wraca?
  • Overwrite idempotency: "idempotentne (nadpisuje)" dla bezpieczeństwa retry. Potwierdź, że aktualizacje znacznika czasu pliku nie powodują fałszywych założeń o świeżości u konsumentów niżej (np. czyszczenie cron widzi nowszy plik).
  • Parallel calls: jeśli dwie retry Hangfire jednocześnie wywołają MirrorAsync(tempPath, ten sam blobFileName), zweryfikuj, że tylko jeden zapis persystuje (atomowe przeniesienie lub blokada pliku).
5

Kontrakty — artefakty, wyniki, odpowiedzi

8 plików2 wysokie

Na co zwrócić uwagę: JSON-owe artefakty przekazywane między etapami, katalog błędów oraz DTO postępu — kształty, na których opiera się reszta systemu.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/StepOutcome.cswysokie
Typ wynikowy etapu podobny do enum: Success vs Skipped (zapisany w DB) vs NoOp (stary/duplikat, bez wpisu w DB) vs BusinessFailure (ładunek błędu zatwierdzony).
  • NoOp jest odróżniany od Skipped, aby zapobiec zatruciu idempotencji — potwierdź, że handlery zgłaszają wyjątek lub zwracają NoOp po wykryciu starej duplikacyjnej egzekucji (np. etap już wykonany w poprzedniej próbie lub równoczesnym zadaniu)
  • IsBusinessFailure + ErrorPayload są wzajemnie wykluczające się ze Skipped/Success — sprawdź, że handler nigdy nie tworzy BusinessFailure z null ErrorPayload lub na odwrót
  • Logika ponownych prób Hangfire musi traktować Success/Skipped/BusinessFailure jako końcowe i NoOp jako sygnał do przerwania bez zatrucia wpisu etapu — potwierdź, że MediatRHangfireBridge to respektuje
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/InboundDeliveryUploadFields.csniskie
Kontrakt nazw pól multipart form-data; jedyne źródło prawdy łączące właściwości request DTO z nazwami pól formularza (poprzez nameof), aby parser i ponowna ekspansja nigdy się nie różniły.
  • Wszystkie wywołania Base(line, property) muszą odpowiadać rzeczywistym indeksom formularza w parserze — potwierdź, że granice pętli linii zapobiegają błędom off-by-one w generowaniu nazw pól (np. jeśli 3 linie, indeksy muszą to być [0],[1],[2])
  • ZipPassword literal 'ZipPassword' nie ma właściwości DTO — sprawdź, że jest używany tylko przez etap rozpakowania i nigdy nie jest serializowany ponownie w rozwinięciu formularza (bez round-trip)
  • Powiązania nameof() są sprawdzane w czasie kompilacji — potwierdź, że jeśli właściwość request DTO zostanie zmieniona, ten plik musi zostać ręcznie zaktualizowany (bez przerwania z punktu widzenia parsera, ale formularz UI się psuje)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Models/ParsedPayloadArtifact.csniskie
Artefakt na próbę niosący przeanalizowaną strukturę żądania (partner, linie bazowe z plikami i podliniami); kolejne etapy hydratyzują obiekty domenowe z tego artefaktu.
  • ProductId nullable w bazie, ale non-nullable w przygotowanej — sprawdź, że mapowanie null→int podczas GenerateKeys nie utraci danych linii
  • ZipPassword opcjonalny — potwierdź, że etap DecryptArchive waliduje obecność przed próbą dekompresji lub gracefully omija
  • TextKeys i Files dzielą linię; potwierdź, że etap rozpakowania egzekwuje niezmienniki strukturalne (brak sierocych plików, reguły głębokości folderów respektowane)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Models/PreparedKeysArtifact.cswysokie
Ostateczny artefakt przygotowanych kluczy (tekst + plik z checksumami + OriginKeyId) plus ExistingOriginKeyIdsToMarkNotNewest; rekonstruowany przez etap Create bez ponownego uruchomienia fabryki plików.
  • ProductId jest teraz non-nullable w PreparedBaseLineArtifact — potwierdź, że analizowanie wymusza obecność lub GenerateKeys odrzuca linie bez produktu
  • KeyId jest generowany w GenerateKeys i ustalony na potem (używany przez etap Upload do nazewnictwa blobów) — sprawdź, że etapy Upload/Create używają identycznego KeyId (brak ponownej generacji)
  • ExistingOriginKeyIdsToMarkNotNewest to lista na poziomie dostawy; potwierdź, że etap DetectDuplicates poprawnie ją wypełnia i etap Create atomowo ją konsumuje dla każdej dostawy
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Responses/InboundDeliveryIngestionProgressResponse.csśrednie
Snapshot postępu (współdzielony przez zapytania odczytowe i transmisje SignalR) przechwytujący stan pozyskania, bieżący etap, timery okresu łaski oraz identyfikatory utworzonej dostawy/zamówienia; dane przejściowe dołączone do transmisji live.
  • Ochrona OriginCode == 0 (linia 100) — potwierdź, że to jedyny stan nie zmigrowany i zaloguj/alertuj, jeśli pojawi się w produkcji (sugeruje starą wiersz)
  • UpdatedAtUtc pochodzi z steps.Max(FinishedAtUtc ?? StartedAtUtc) — sprawdź, że to obsługuje pustą listę kroków (domyślnie) i stany częściowego ukończenia poprawnie
  • GracePeriodStartedAtUtc + GracePeriodSeconds definiują okno przed-tworzenie; DeliveryCompletionScheduledAtUtc + DeliveryGracePeriodSeconds definiują okno po-tworzeniu — potwierdź, że logika odliczania UI nigdy nie przekracza tych granic
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Responses/IngestionStepProgressDto.csniskie
DTO postępu na etap (typ, status, znaczniki czasu, ładunek błędu) mapowany z domeny IngestionStepExecution; zwracany na liście w InboundDeliveryIngestionProgressResponse.
  • ErrorPayload jest nullable string — potwierdź, że jest wypełniany tylko gdy StepStatus to Failed (UI renderuje warunkowo na tym; sprawdź, że brak sierocych JSON-ów błędów)
  • FinishedAtUtc jest nullable — potwierdź, że kroki Skipped/Running mają null i Succeeded/Failed mają non-null (brak przypadku gdzie oba są wypełnione)
  • Pole Attempt odzwierciedla próbę pozyskania rodzica — potwierdź, że ponowne próby wieloprobe re-populują pełną listę kroków (nie append-only), aby UI widział zniknięcie wpisów starej próby
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Results/IngestionErrors.csśrednie
Katalog dobrze opisanych, skategoryzowanych fabryk błędów (kod + kategoria + tytuł + wiadomość + wyjaśnienie + temat) używanych przez wszystkie etapy potoku do spójnego raportowania błędów.
  • Kody błędów to niezmienialne stringi używane jako klucze idempotencji przez klientów — sprawdź, że brak literówek lub przyszłych zamian na enumy, które mogłyby przerwać wyszukiwanie błędów
  • KeyAlreadyInStock vs KeyAlreadyLoaded vs KeyNotDownloaded — potwierdź, że te trzy stany są wzajemnie wykluczające się i każdy zmapowany na dokładnie jeden scenariusz podczas DetectDuplicates
  • Błędy archiwum (ArchiveUnreadable, ArchivePasswordProtected, ArchiveWrongPassword) — sprawdź, że każdy jest rzucany z dokładnie jednej ścieżki kodu w etapie UnpackArchives (brak silent fallback)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Results/IngestionStepFailure.csśrednie
Koperta wyniku (lista errors[] + Summary) serializowana do ErrorPayload etapu (camelCase JSON); przejściowy kontener podczas egzekucji etapu, zatwierdzony po zakończeniu etapu.
  • Add() zwraca this dla łańcuchowania fluent — potwierdź, że wszystkie handlery etapów zwracają IngestionStepFailure (nie null) nawet w przypadku powodzenia (poprzez ochronę IsFailed)
  • ToErrorPayload() używa CamelCasePropertyNamesContractResolver z NullValueHandling.Ignore — sprawdź, że Summary=null w przypadku powodzenia produkuje '{summary:null,errors:[]}' i UI może deserializować
  • BuildSummary() grupuje po Kategoriach i formatuje jako 'N problems found (A × CatA, B × CatB)' — potwierdź, że lokalizacja/i18n nie jest potrzebna (wiadomość jest dla admina)
6

Komendy i bazowa klasa background command

22 plików8 wysokie

Na co zwrócić uwagę: DTO komend (nośniki IngestionId / Attempt / UserId) oraz bazowa klasa background command, która przywraca kontekst użytkownika. Sprawdzić threading attempt i user.

src/WOCK.WholeSale.Application/BackgroundCommandHandler.csniskie
Generyczna bazowa klasa dla handlera background command, przywraca kontekst użytkownika podczas wykonywania zadania Hangfire
  • Przywrócenie kontekstu użytkownika odbywa się via backgroundService.SetUserContext przed HandleAsync — dopasowuje wzorzec IntegrationEventHandler w wierszu 22
  • Zawijanie transakcji jest delegowane do zachowania pipeline (TransactionVoidCommandBehavior) — komentarz w wierszu 14 poprawnie to odnotowuje, unikając duplikowania scope
  • Wymuszenie kontraktu IBackgroundCommand w ograniczeniu generycznym w wierszu 18 — wymaga właściwości UserId dla kontekstu użytkownika
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/AwaitGracePeriodStepCommand.csśrednie
Krok 9: parkuje ingestion w oknie grace (stan pauzalny) i planuje opóźniony converge; pozwala Stop/Resume
  • Zweryfikować, że handler przechodzi status do Grace i rejestruje czas rozpoczęcia grace + token
  • Potwierdzić, że opóźniony task converge używa tego samego IngestionId + Attempt, ale zawiera GraceToken
  • Sprawdzić, że czas trwania okna grace jest wymuszany; zweryfikować, że Resume preplannuje converge z tym samym opóźnieniem
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/CreateInboundDeliveryIngestionCommand.csśrednie
Komenda synchroniczna wypalana przez upload controller; tworzy agregat początkowy i enqueueuje pierwszy krok (ParseRawPayload)
  • Potwierdzić, że handler czyta UserId z IUserProvider i wstrzykuje go do pierwszego zakolejkowanego kroku
  • Zweryfikować, że IngestionId jest już wygenerowany przez controller przed wysłaniem; sprawdzić idempotencję przy re-fire
  • Upewnić się, że RawPayloadVersionKey (opcjonalny) jest przetransportowany do operacji blob'ów w kroku converge
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/CreateInboundDeliveryStepCommand.cswysokie
Krok 10 (converge): tworzy delivery + wygenerowany order, uploaduje klucze do blob'u, oznacza ingestion jako Succeeded
  • Zweryfikować GraceToken check — handler no-ops jeśli token nie pasuje (stare zadanie z zastąpionego okna grace)
  • Potwierdzić, że tworzenie delivery jest atomowe z uploadem blob'u kluczy (timeout 900s w jednej transakcji)
  • Sprawdzić obsługę linii overflow: SupplyInboundOrders generuje Inbound Order dla kluczy overflow; converge zapisuje je inline (scoped sink, → IBO wygenerowane) — bez komendy post-commit
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/CreateInboundDeliveryStepCommandTimeout.csśrednie
Przesłonięcie timeout transakcji: przyznaje 900s (15 minut) dla kroku converge do obsługi dużych deliveries
  • Zweryfikować, że timeout jest zarejestrowany w DI i wyłapywany przez TransactionCommandBehavior
  • Sprawdzić, że 900s jest wystarczające dla największego oczekiwanego delivery (upload blob'u kluczy + zapisy DB)
  • Potwierdzić, że timeout stosuje się tylko do CreateInboundDeliveryStepCommand, nie do innych kroków
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/DeleteInboundDeliveryIngestionCommand.csniskie
Wyzwalane przez użytkownika lub admin: trwale usuwamy nieudane/zatrzymane/zastąpione ingestion + przechowywany payload
  • Zweryfikować, że handler sprawdza status (pozwala delete tylko jeśli Failed/Stopped, nie in-flight)
  • Potwierdzić, że handler wywoła purge do usunięcia raw payload + artifacts (orchhestruje cleanup)
  • Sprawdzić idempotencję: usuwanie już-usuniętego ingestion (lub nieistniejącego) gratuitously succeeds lub jasny error
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/DetectDuplicatesStepCommand.cswysokie
Krok 7: sprawdza wygenerowane klucze względem istniejących deliveries + zarezerwowanych kluczy aby złapać przypadkowe re-uploady
  • Zweryfikować, że check queryuje zarówno Delivery (committed klucze) jak i tabelę rezerwacji (in-flight ingestions)
  • Potwierdzić, że attempt guard zapobiega re-checkingowi po Reserve (idempotentny read)
  • Sprawdzić obsługę błędów: częściowe duplikaty (część kluczy istnieje, część nie) — przejść do Failed ze względem
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ExpandArchivesStepCommand.cswysokie
Krok 2: rozpakowuje pliki ZIP do struktury line+dependent; waliduje sygnatury archiwów
  • Zweryfikować, że handler odrzuca zniekształcone/uszkodzone ZIPy przed mutowaniem agregatu (bezpieczeństwo idempotencji)
  • Potwierdzić, że rozpakowywanie ZIP jest deterministyczne (kolejność pliku stabilna); sprawdzić pod kątem path traversal/symlink attack
  • Sprawdzić, że częściowe/niekompletne rozpakowywania wyzwalają failure (rollback) nie stan pośredni
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/GenerateKeysStepCommand.cswysokie
Krok 5: generuje unikalne klucze dla każdej linii; obsługuje linie overflow (nadmiar stanu)
  • Zweryfikować, że generowanie kluczy używa kryptograficznie bezpiecznego random (UUID/GUID); sprawdzić kolizje/reuse
  • Potwierdzić, że linie overflow są oddzielone od zwykłych linii; sprawdzić liczbę partycji vs maksymalny capacity delivery
  • Sprawdzić attempt guard: jeśli wskrzeszenie po inkrementacji attempt, zweryfikować, że klucze są regenerowane (idempotentny format)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/InboundDeliveryIngestionStepCommand.cswysokie
Abstrakcyjna bazowa klasa dla wszystkich 11 komend kroku pipeline; wymusza IngestionId + Attempt + UserId (wymagane) dla filtrowania stałych zadań
  • Zwerifikować, że pole Attempt jest używane przez każdy handler kroku aby zabezpieczyć przed nieaktualnymi (retry/resume) zadaniami wykonywanymi dwukrotnie
  • Potwierdzić, że UserId jest wymagany w compile time i nie może być null/empty w żadnej konstrukcji step command
  • Sprawdzić, że step handlery porównują Attempt względem obecnego attempt agregatu przed przystąpieniem
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/MarkIngestionTerminallyFailedCommand.csśrednie
Zakolejkowany przez Hangfire terminal-failure filter gdy krok wyczerpie retries; przechodzi agregat do Failed
  • Zweryfikować, że pole Attempt blokuje nieaktualne failure (job ze starego attempt jest ignorowany)
  • Potwierdzić, że handler sprawdza obecny status przed przechodzeniem (bez duplikowania-przechodzenia jeśli już Failed)
  • Sprawdzić, że string Reason jest przechwycony na agregacie dla wyświetlenia UI; zweryfikować limit długości lub obcinanie
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ParseRawPayloadStepCommand.csśrednie
Krok 1: parsuje przechowywany raw payload do struktury linii (struktura delivery)
  • Zweryfikować, że handler sprawdza czy Attempt pasuje do aggregate.Attempt przed mutowaniem stanu
  • Potwierdzić, że parser zachowuje kolejność linii i waliduje granice pliku (bez obcinania/uszkodzenia)
  • Sprawdzić, że failure parsowania przechodzi agregat do Failed (nie pozostawia Queued); zweryfikować klasyfikację błędu
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/PurgeIngestionRawPayloadCommand.csniskie
Fire-and-forget cleanup: usuwa ingestion raw body + expand artifacts po pomyślnym converge
  • Zweryfikować, że handler jest zaplanowany post-converge-commit (delete blob'u nie może rollback nieudanego converge)
  • Potwierdzić, że purge jest idempotentny (usuwanie już-brakujących plików nie błęduje)
  • Sprawdzić, że failure purge (timeout delete blob'u) jest logowany ale nie faila ingestion (fire-and-forget semantyka)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ReserveKeysStepCommand.cswysokie
Krok 6: atomowo rezerwuje wszystkie wygenerowane klucze aby jednocześnie-istniejące ingestions nie mogły zarezerwować te same klucze
  • Zweryfikować, że rezerwacja używa row lock bazy danych (SELECT FOR UPDATE lub equivalent) wewnątrz transakcji
  • Sprawdzić, że jednocześnie-istniejące attempt rezerwacji serializują (brak race condition na tym samym zestawie kluczy)
  • Potwierdzić, że attempt guard blokuje nieaktualne rezerwacje; zweryfikować rollback czyści rezerwację na failure
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ResolveAndValidateProductsStepCommand.cswysokie
Krok 4: rozwiązuje SKU do produktów; waliduje format SKU, istnienie i stan produktu
  • Potwierdzić, że handler sprawdza wszystkie SKU w sparsowanych liniach (bez pominięcia); zweryfikować lista failure + przyczyna przechwycone
  • Sprawdzić, że invalid format SKU blokuje progresję; zweryfikować usunięty/inactive produkt blokuje z jasnym błędem
  • Upewnić się, że lookups produktu są transakcyjne (brak read-committed ghosts); zweryfikować attempt guard
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/RestartInboundDeliveryIngestionCommand.csśrednie
Wyzwalane przez użytkownika: restartuje nieudane ingestion z poprawioną raw payload (nowa wersja uploadowana przez controller)
  • Zweryfikować, że handler sprawdza status agregatu (fails jeśli nie Failed)
  • Potwierdzić, że RawPayloadVersionKey jest zaktualizowany; Attempt jest inkrementowany (zastępuje poprzedni attempt)
  • Sprawdzić, że PartnerId override (jeśli dostarczona) jest aplikowany przed re-enqueuingiem; zweryfikować walidację
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ResumeInboundDeliveryIngestionCommand.csśrednie
Wyzwalane przez użytkownika: resumes ingestion wznowiony w oknie grace; re-schedules opóźniony converge
  • Zweryfikować status guard: tylko valid gdy status to Grace + StoppedDuring == Grace
  • Potwierdzić, że resume ponownie wykorzystuje ten sam Attempt + GraceToken (nie regenerować klucze)
  • Sprawdzić, że opóźnienie converge jest recalculated z Resume time (nie z oryginalnego grace start)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/StartInboundDeliveryIngestionCommand.csśrednie
Wyzwalane przez użytkownika: restartuje Failed/Stopped ingestion z kroku 1, ponownie wykorzystując przechowywany raw payload
  • Zweryfikować, że handler sprawdza status agregatu (fails jeśli nie Failed lub Stopped)
  • Potwierdzić, że handler inkrementuje Attempt (żeby stare jobsy z poprzedniego attempt były ignorowane)
  • Sprawdzić, że pierwszy zakolejkowany krok dostaje nową wartość Attempt + UserId z IUserProvider (nie z komendy)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/StopInboundDeliveryIngestionCommand.csśrednie
Wyzwalane przez użytkownika: wznawia queued/running ingestion; tylko valid z Queued lub Running status
  • Zweryfikować status guard: blokuje Stop jeśli już Grace/Failed/Succeeded
  • Potwierdzić, że in-flight jobsy nie są cancelled (stale-job guard via Attempt obsługuje je)
  • Sprawdzić, że Stop w oknie grace rejestruje czas stop + przyczynę dla logiki Resume
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/UploadKeysStepCommand.cswysokie
Krok 8: uploaduje wygenerowane+zarezerwowane klucze do blob storage i PVC przed converge; pre-staging dla tworzenia delivery
  • Zweryfikować, że upload jest idempotentny (re-uploading tego samego ingestion ponownie wykorzystuje tę samą ścieżkę blob, bez duplikatów)
  • Sprawdzić, że partial blob upload failure (niektóre pliki napisane, niektóre nie) jest wykrywany; zweryfikować rollback
  • Potwierdzić, że blob transaction scope pasuje do EF transakcji (obie commitują lub obie rollbackują)
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Commands/ValidatePartnerStepCommand.csśrednie
Krok 3: weryfikuje partner istnieje i jest aktywny; waliduje względem reguł partnera
  • Potwierdzić, że handler fetcha partnera z repository wewnątrz transaction scope (bez nieaktualnych reads)
  • Zweryfikować, że partner inactive/deleted stan blokuje progresję (przechodzenie do Failed)
  • Sprawdzić, że partner lookups używają attempt guard aby uniknąć re-walidacji po Resume
7

Cykl życia podstawowy i handler wejścia

2 plików1 wysokie

Na co zwrócić uwagę: Wspólny cykl życia obiektu handler kroku (guardy przed nieaktualnym stanem/zatrzymaniem/idempotencją, zapis kroku, umieszczenie następnego, transakcja) oraz wejście HTTP, które umieszcza pobieranie w kolejce. Pliki o największym znaczeniu w PR.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/CreateInboundDeliveryIngestionCommandHandler.csśrednie
Handler wejścia: tworzy i utrwala aggregate główny pobierania w stanie początkowym, przygotowując pierwszego kroku do umieszczenia w kolejce przez wywołującego.
  • Idempotentność zduplikowanego IngestionId (linia 22): potwierdź, że repository.Add() + SaveChangesAsync() na zduplikowanym ID powoduje błąd więzu DB (nie cichą nadpisanie)—sprawdź, czy wywołujący chroni przed podwójnym przesłaniem tego samego IngestionId w oknie wyścigu.
  • Zakres transakcji (linia 24): potwierdź, że SaveChangesAsync() dziedziczy TransactionScope zachowania polecenia zewnętrznego (IsolationLevel.ReadCommitted, timeout 300s)—jeśli nie, niezatwierdzone aggregate mogłoby być widoczne dla pierwszego kroku przed pełnym utworzeniem.
  • Zapisane na stałe IngestionOrigin.WholeSale (linia 20): potwierdź, że jest to celowe (nie zastępnik)—jeśli oczekiwane są inne pochodzenia, cicho przypisze błędne pochodzenie.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/InboundDeliveryIngestionStepHandler.cswysokie
Abstrakcyjna baza aranżująca cykl życia na krok: załaduj aggregate, ostrzeż przed nieaktualnymi próbami i zatrzymaniami, wykonaj pracę domeny, zaklasyfikuj wyjątek jako biznesowe (bez ponawiania) vs przejściowe (ponowienie), utrwal wynik i umieść następny krok w kolejce.
  • Sprawdzenie wersji Attempt (linia 49): potwierdź, że jeśli starszy krok zostanie powtórzony po awarii pierwszego kroku nowszej próby (przed zatwierdzeniem jego wiersza), starszy krok nie zostanie błędnie skrócony—czy HasStepCompleted(StepTypeCode, oldAttempt) zwraca false, jeśli nie istnieje wiersz dla nowszej próby?
  • Atomowość flagi żądania zatrzymania (linie 147–158): upewnij się, że wyczyszczenie pamięci podręcznej (linia 154) i zapis Stopped DB (linia 152) są serializowane jako jedna jednostka—sprawdź brak wyścigu między ponowionym obserwowaniem starej flagi a współbieżnym handlowaniem zatrzymaniem czyszczącym je, pozostawiając obie kroki myślące, że posiadają wstrzymanie.
  • Klasyfikacja wyjątku błędu biznesowego (linia 199): potwierdź, że wszystkie typy wyjątków zgłoszone przez konkretne handlery kroków są jawnie obsługiwane w IsBusinessFailure()—jeśli nowy wyjątek ujdzie niezklasyfikowany, jest traktowany jako przejściowy (ryzyko pętli ponawiania).
8

Procedury obsługi etapów potoku · 1–5 (parsowanie → generowanie kluczy)

5 plików2 wysokie

Na co zwrócić uwagę: Przeczytaj każdą metodę ExecuteAsync: artefakt wejściowy/wyjściowy, klasyfikacja awarii biznesowej i kontrole magazynu/kluczy w Generate keys.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ExpandArchivesStepHandler.csśrednie
Krok 2: Rozpakowuj archiwa ZIP, mutuj listę baseLine.Files w pamięci, przepisz artefakt; pomiń, jeśli brak archiwów (zwróć status Skipped).
  • Mutacja listy podczas wyliczania: linie 54, 58—utwórz snapshot archiwów, następnie Remove z baseLine.Files podczas pętli foreach; sprawdź, czy modyfikacja nie uszkodzi listy bazowej ani nie pominięcia wpisów.
  • Obsługa uszkodzenia archiwum: linie 63-76 gałąź na ReadError vs HasUsableEntries; czy kolizja WrongPassword + puste archiwum jest testowana? Przypadek brzegowy: archiwum z błędem odczytu I zaszyfrowanymi wpisami.
  • Brakujący artefakt: linia 46 rzuca InvalidOperationException jeśli artefakt ParsedPayload nieobecny — w choreografii, co wyzwala ponowną próbę? Powinno to być BusinessFailure, aby pasować do kontraktu kroku?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/GenerateKeysStepHandler.cswysokie
Krok 5 (intensywny): Parsuj pliki, rozwiąż klucze tekstowe/plikowe, uruchom kontrole dostępności (istniejące klucze, wieża poboru, nie pobrane, duplikaty), wyemituj artefakt przygotowanych kluczy. Tylko do odczytu danych dostawy.
  • Logika wykrywania duplikatów (linie 197-204, 267-274): gdy allowDuplicates=false, całe żądanie nie powiedzie się jeśli JAKIKOLWIEK klucz jest duplikatem; gdy true, originIdsToMark zbiera duplikaty do późniejszego 'oznaczenia jako nie najnowsze'. Przypadek testowy: 10 kluczy, 1. jest duplikatem—czy żądanie przerywa się czy kontynuuje oznaczając tylko 1.?
  • Asymetria klucza tekstowego vs plikowego: ścieżka TextKeys (linia 173-214) vs ścieżka FileKeys (linia 217-293) mają rozbieżną logikę agregacji błędów. Linia 175 filtruje 'notDownloaded' jako 'existing.Where(key => key.IsNotDownloaded && key.IsDuplicate)'—dlaczego oba warunki? FileKeys linia 245 sprawdza tylko IsDuplicate. Pierwotna przyczyna: czy klucz tekstowy może być NotDownloaded ale nie być duplikatem?
  • Zawłaszczenie krótkiego obwodu kontroli wieży poboru (linie 177-185, 247-254): jeśli onPickTower.Any(), natychmiast zwraca pustą listę—czy to ukrywa kolejne błędy walidacji (np. uszkodzone pliki) czy to jest zamierzone?
  • Niezgodność liczby linii zależnych (linia 115-117): porównuje depKeys.Count vs baseKeys.Count, ale baseLineKeyCount (linia 103) to tylko plik+tekst na linii bazowej—nie zawiera linii zależnych. Czy porównanie jest poprawne? Prawdopodobny błąd: powinno porównać depKeys.Count vs baseLineKeyCount tylko jeśli brak plików/kluczy tekstowych na linii zależnej.
  • Raportowanie postępu przy opóźnieniu (linie 135-138): obliczenie paceMs 'Math.Max(totalLines, 1)' nigdy nie trafi w przypadek 1—OK, ale sprawdź czy opóźnienie nie kumuluje się w timeout.
  • Wczesne wyjście parsowania pliku (linia 224): fileFactory.CreateFiles() może dołączać wiele wyników na plik—sprawdź czy pętla nie przetwarza dwukrotnie tego samego pliku przy ponownej próbie.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ParseRawPayloadStepHandler.cswysokie
Krok 1: Parsuj wieloczęściową treść, waliduj ją, utrwal artefakt parsowanej ładunku i ustaw flagi partnera/duplikatów na agregacie ingestion.
  • Wyjątki deserializacji artefaktu: czy ParseAsync nie powiedzie się czyszczo jeśli surowa treść jest zniekształcona (nie tylko walidacja przez CreateInboundDeliveryCommandValidator)? Sprawdź czy nie ma cichego obcięcia strumieni wieloczęściowych.
  • Idempotencja: jeśli uruchomisz ponownie przy tym samym attempt po częściowym powodzeniu, czy ponowne parsowanie nadpisze artefakt czy wykryje duplikat? Sprawdź ścieżkę ładowania artefaktu nie wyściga SaveArtifactAsync.
  • Mutacja ID partnera: ApplyParsedHeader() modyfikuje agregat ingestion poza zakresem transakcji — sprawdź czy nema konkurencyjnych zapisów do PartnerId/AllowDuplicates jeśli ponowne próby kroku występują podczas okna grace.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ResolveAndValidateProductsStepHandler.csśrednie
Krok 4: Załaduj parsowany artefakt, sprawdź liczbę linii + długość notatki, pobierz produkty, waliduj stan (nie wyłączony/wstrzymanie), oznacz linie nieprzetwarzalne.
  • Przetwarzalność podlinii: linie 80, 92-93 ponownie sprawdzają predykat IsProcessable(); sprawdź czy jest identyczny w obydwu Kroku 4 i Kroku 5 (GenerateKeys też sprawdza linia 80). Każde rozbieżność = niespójna raportowanie błędów.
  • Pobieranie produktu: linia 60 GetAllAsync(predicate) ładuje wszystkie produkty pasujące do ID; jeśli produkt zostanie usunięty pomiędzy Krokiem 3 i Krokiem 4, czy raportowanie błędów rozróżnia 'produkt nie znaleziony' vs 'wyłączony'? Linie 97-111 skanują tablicę wielokrotnie (O(n²))—wydajność OK dla MaximumDeliveryLines?
  • Błąd off-by-one na liczbie linii: linia 48 używa .Count (nie .Count()?) — sprawdź czy artifact.BaseLines to List<> aby Count był cachowany, nie przeoceniony na każde sprawdzenie.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ValidatePartnerStepHandler.csśrednie
Krok 3: Wykonaj zapytanie do repozytorium partnera; biznesowa awaria jeśli PartnerId null lub partner nie istnieje.
  • Wyścig: PartnerId ustawiane przez Krok 1, zapytywane tutaj; jeśli Krok 1 i 3 zachodzą na siebie podczas ponownych prób, czy PartnerId jest gwarantowany napisany przed odczytaniem? Sprawdź czy przeładowanie agregatu ingestion z bazy danych odzwierciedla ApplyParsedHeader() z Kroku 1.
  • Sprawdzenie null na Partner.Id: linia 35 sprawdza 'ingestion.PartnerId is null', ale co jeśli PartnerId to 0 lub nieprawidłowe? Czy Domena pozwala PartnerId jako nullable int?
9

Procedury kroków potoku · 6–10 (rezerwacja → konwergencja)

5 plików5 wysokie

Na co zwrócić uwagę: Krytyczna dla współbieżności część: rezerwacja Redis, deduplikacja w ramach ładunku + zwolnienie, upload blob, harmonogram grace period i konwergencja zabezpieczona tokenem.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/AwaitGracePeriodStepHandler.cswysokie
Krok 9: Przejście do stanu grace period, zaplanowanie konwergencji po N sekundach, umożliwienie użytkownikowi Stop/Resume; zwraca null (bez natychmiastowego następnego kroku).
  • GraceToken staleization: linia 43 wywołuje BeginGracePeriod(), następnie linia 48–56 planuje konwergencję z tym tokenem. Jeśli BeginGracePeriod() mutuje stan ingestion ale SaveChanges nie powiedzie się, zaplanowane zadanie się aktywuje a CreateInboundDelivery sprawdzi token (linia 61) — potwierdź że strażnik tokenem zapobiega pojawieniu się fikcyjnej dostawy.
  • Kolejność Schedule-after-mutation (linia 45–47): Schedule() nie jest transakcyjny. Jeśli commit się wycofuje, zadanie konwergencji się aktywuje ale strażnik NoOp (sprawdzenie statusu + tokenu w CreateInboundDelivery linia 61) go po cichu ignoruje. Czy to zamierzony model idempotencji? Potwierdź że brak efektów ubocznych sierocych zadań konwergencji.
  • Grace period = 0 przypadek brzegowy: timers.InboundDeliveryGracePeriod może wynosić 0—czy TimeSpan.FromSeconds(0) planuje natychmiast? Sprawdź brak race condition jeśli użytkownik natychmiast Resume przed wygaśnięciem grace period.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/CreateInboundDeliveryStepHandler.cswysokie
Krok 10 (konwergencja): Rekonstrukcja kluczy/linii z artefaktu, oznaczenie starych kluczy jako not-newest, tworzenie InboundDelivery + wygenerowanego Order, atomowo z zakończeniem ingestion. Usuwa pliki tymczasowe.
  • Strażnik idempotencji (linia 51–54): jeśli CreatedInboundDeliveryId jest już ustawione, zwraca Success(). Ale przy pierwszym rzeczywistym wykonaniu + retry po commit, ten strażnik po cichu pomija rekreację—czy no-op jest akceptowalny czy powinien rzucić/error? Ryzyko: jeśli zadanie aktywuje się dwa razy przed pierwszym commitem, drugie aktywuje się zanim strażnik zacznie działać, tworząc duplikat dostawy.
  • Grace-token staleization (linia 61): status + strażnik tokenu zwraca NoOp (nie Success). NoOp pomija zapis wiersza kroku zgodnie z docstring (linia 58–60). Ale co jeśli sieroce zadanie aktywuje się PO tym jak prawidłowe się zakończy? Oba zwrócą NoOp → brak rekordu → brak sygnału dla wywołującego. Czy to bezpieczne?
  • Usuwanie plików tymczasowych (linia 117–138): try-catch łyka wszystkie wyjątki (linia 136). Jeśli plik tymczasowy jest zablokowany (wciąż się uploaduje?), po cichu pozostaje. Ryzyko: czyszczenie sierot może go nie znaleźć jeśli format ścieżki się zmieni. Potwierdź że czyszczenie jest best-effort i konwergencja się nie łamie na sierocach.
  • Mark-not-newest (linia 93–97): wywoływane PRZED utworzeniem dostawy. Jeśli ta aktualizacja się nie powiedzie i wycofuje, żadna dostawa nie istnieje—idempotentne. Ale jeśli powiedzie się a tworzenie dostawy się nie powiedzie, stare klucze są oznaczone jako not-newest na zawsze. Czy to akceptowalny? Zweryfikuj semantykę domeny.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/DetectDuplicatesStepHandler.cswysokie
Krok 7: Skanowanie wszystkich przygotowanych kluczy pod kątem deduplikacji w ramach ładunku (match tekstu/checksum); zwolnienie rezerwacji przy niepowodzeniu aby inne ingestion nie były zablokowane.
  • Release-on-failure jest krytyczny: ReleaseAsync() wywoływane tylko jeśli `failure.IsFailed` (linia 70). Zweryfikuj czy failure.Add() jest komprehensywny—czy IngestionErrors.DuplicateInLoad() łapie wszystkie typy duplikatów? Ryzyko: po cichu no-ops jeśli tekst ani checksum duplikaty nie zostały wykryte.
  • Ryzyko double-release: jeśli ten handler się nie powiedzie i retry, czy ReleaseAsync() idempotentnie obsługuje już zwolnione klucze? Czy retry rzuca?
  • Przypadek brzegowy: konstruowanie listy AllKeys (linia 43–50) dokładnie oddaje logikę ReserveKeys. Jeśli PreparedKeysArtifact jest uszkodzony/brakuje w środku potoku, oba handlery rzucają InvalidOperationException—potwierdź że wybór wyjątku jest poprawny (nie business failure).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ReserveKeysStepHandler.cswysokie
Krok 6: Atomowo rezerwuje wszystkie klucze ładunku via usługa rezerwacji; zamyka race condition między concurrent GenerateKeys przejściami poprzez blokowanie duplikatowych kluczy.
  • Czy TryReserveAsync poprawnie atomowo wyklucza klucze rezerwowane przez concurrent ingestion? Zweryfikuj że detekacja konfliktów jest total-order (nie lost-update race).
  • Ryzyko kolizji hasha: przedrostek tekstu klucza 't:' vs przedrostek klucza pliku 'f:' są odrębne, ale zweryfikuj brak kolizji z `key.Checksum` (nullable File?). Przypadek brzegowy: czy null checksum/FileName jest obsługiwany?
  • Brak logiki rollback: jeśli rezerwacja się powiedzie, handler zwraca Success ale następny krok się nie powiedzie (np. DetectDuplicates)—czy klucze są trzymane do zwolnienia via DetectDuplicates.ReleaseAsync()?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/UploadKeysStepHandler.cswysokie
Krok 8: Batch-upload kluczy pliku do blob storage + PVC keys-storage równolegle przed konwergencją; pominięcie jeśli tylko tekstowe. Retry uploaduje idempotentnie.
  • Zachowanie pliku tymczasowego dla idempotencji: docstring mówi 'deleted by converge step'—ale UploadKeyAsync rzuca jeśli checksum się nie zgadza (linia 106). Po retry po transientnym blob failure, pliki tymczasowe muszą wciąż istnieć. Potwierdź że logika czyszczenia temp jest tylko w konwergencji, nigdy tutaj.
  • Race condition parallel upload (linia 100): Task.WhenAll czeka na blobTask dwa razy (linia 100, następnie linia 102 odczytuje wynik). Czy czekanie na blobTask dwa razy działa w .NET? Powinno cachować wynik: `var uploadedChecksum = await blobTask;` przed linią 100.
  • Rozmiar batch = 5000 (linia 40): brak backpressure lub agregacji błędów na batch. Jeśli batch 2 się nie powiedzie w środku chunka, już udane batche 1+ są sierocze—idempotencja musi obsługiwać częściowe uploady przy retry.
10

Procedury kontrolne

8 plików3 wysokie

Na co zwrócić uwagę: Stop / wznowienie / start / restart / usunięcie / rejestracja / czyszczenie / błąd końcowy. Sprawdź flagi cache vs zapis agregatu, buforowanie próbek i zwolnienie rezerwacji.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/DeleteInboundDeliveryIngestionCommandHandler.cswysokie
Trwałe usunięcie kaskadowe: wiersz agregatu + kroki + cache + rezerwacje + pliki PVC (idempotentne — brakujący agregat jest OK)
  • Idempotencja i kolejność (line 26-36): sprawdzenie null agregatu przed validacją — czy pominięcie EnsureCanBeDeleted gdy agregat zniknie tworzy lukę do usunięcia Running/Succeeded?
  • Czyszczenie kaskadowe (lines 41-43): RemoveAsync, ReleaseAsync, DeleteAllAsync działają sekwencyjnie — jeśli DeleteAllAsync zawiedzie, cache + rezerwacje już wyczyszczone, pozostawiając PVC osierocony. Czy to akceptowalne czy kolejność powinna być odwrócona?
  • Idempotencja usunięcia magazynu: DeleteAllAsync wywoływane dwa razy (raz ścieżka null-agregatu, raz normalna ścieżka) — czy jest to bezpieczne czy może wyrzucić wyjątek?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/MarkIngestionTerminallyFailedCommandHandler.cswysokie
Haczyk końcowego błędu Hangfire'a: oznaczenie ingestion Failed (idempotentne przy niezgodności próby lub już terminalnym stanie)
  • Ochrona próby (line 25): jeśli ingestion.AttemptNumber != command.Attempt, cicho wróć — czy to prawidłowo osieraca błąd końcowy dla restartu próby N+1?
  • Ochrona już-terminalnego (line 34): MarkTerminallyFailed sprawdza StatusCode i nie robi nic jeśli już Succeeded/IboGenerated/Failed/Stopped — czy to jest poprawne czy Failed+Failed powinno być odrzucone bardziej surowo?
  • Zwolnienie rezerwacji (line 43): wywoływane bezwarunkowo po pomyślnym MarkTerminallyFailed — czy zwolnia klucze dla prawidłowej próby czy mogłoby się zderzić z równoczesnym Restart?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/PurgeIngestionRawPayloadCommandHandler.csniskie
Asynchroniczne czyszczenie: usunięcie surowych ciał + artefaktów z PVC, oznaczenie agregatu oczyszczonego (idempotentne; brakujący agregat jest OK)
  • Kolejność (line 18): DeleteAllAsync dzieje się PRZED GetWithStepsAsync — jeśli DeleteAllAsync wyrzuci przejściowy błąd, następna ponowna próba Hangfire'a usuwa ponownie (już zniknęło) ale następnie oznacza oczyszczone; czy to jest bezpieczne czy usunięcie może wyrzucić błąd nieodwracalny?
  • Brakujący agregat (line 20): jeśli agregat jest znikięty, oznaczenie-oczyszczenia jest pominięte — czy pieczęć czyszczenia cron-u zakłada purged=true tylko gdy zarówno magazyn jest wyczyszczony I wiersz agregatu istnieje? Czy to luka logiczna?
  • Współbieżny handler Delete: jeśli DeleteInboundDeliveryIngestionCommandHandler uruchamia się jednocześnie, czy ponownie wywołuje DeleteAllAsync podczas gdy ten jest na środku usuwania? Dwa współbieżne usunięcia na tym samym magazynie?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/RestartInboundDeliveryIngestionCommandHandler.cswysokie
Zwiększenie próby i ponowne uruchomienie od początku (osieraca artefakty i przygotowane klucze poprzedniej próby)
  • Bump próby (line 36): RestartFromBeginning zwiększa AttemptNumber — czy zadania w locie z próby N-1 gwarantowo staną się no-opami gdy zobaczą próbę N?
  • Token współbieżności: czy bump wersji agregatu przy restarcie pozwala na równoczesną konwergencję z próby N-1 aby zatwierdziła się najpierw, pozostawiając restart w stanie częściowym?
  • Czyszczenie rezerwacji: restart zwiększa próbę ale stare rezerwacje są wciąż powiązane z ID ingestion — czy TryReserveAsync re-claim przy próbie N powiedzie się czy detekcja kolizji starej próby N-1 hashów?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/ResumeInboundDeliveryIngestionCommandHandler.csśrednie
Ponowne uzbrojenie okresu wznowienia na tej samej próbie (wyczyść przestarzałą flagę zatrzymania, wyemituj świeży token wznowienia, zaplanuj nową konwergencję)
  • Walidacja przed skutkami ubocznymi (line 42): EnsureCanBeResumed chroni prawidłowo przed ClearStopRequestAsync — czy reguła domeny zapobiega wznowieniu z nie-Stopped stanu?
  • Unieważnienie tokena wznowienia (line 51): ResumeGracePeriod regeneruje token — czy stara oczekująca praca konwergencji (pre-pause) otrzymuje stary token i prawidłowo nie robi nic?
  • Ochrona próby: ingestion.AttemptNumber pozostaje taki sam podczas wznowienia — czy przestarzałe prace Hangfire'a sprzed wznowienia uruchamiają się i sprawdzają go?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/StartInboundDeliveryIngestionCommandHandler.csśrednie
Ponowne uruchomienie z Stopped/Failed na tej samej próbie (musi odrzucić period wznowienia aby uniknąć kolizji harmonogramu)
  • Ochrona wznowienia w okresie (line 33): IsPausedInGracePeriod sprawdzenie chroni ścieżkę tylko wznowienia — jeśli użytkownik wywołuje Start na grace-paused, czy błąd jest wystarczająco jasny i czy Resume zostaje zamiast tego wywołane?
  • Cache flagi zatrzymania wyczyszczone (line 40): czy czyszczenie jest idempotentne jeśli wywoływane dwa razy? Czy handler kroku poniżej sprawdza flagę od razu czy istnieje wyścig?
  • Rozsyłka zdarzenia Queued (line 45): Start() opublikuje InboundDeliveryIngestionQueuedEvent — czy istniejąca praca konwergencji w locie (z poprzedniego stop/resume) zostaje zastąpiona czy zablokowana przez re-Queued event?
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CommandHandlers/StopInboundDeliveryIngestionCommandHandler.csśrednie
Sygnał pauzy (cache-only flaga zatrzymania podczas Running/Queued, lub jedynym pisarzem Stopped w oknie wznowienia)
  • Cache-only vs. gałąź okresu wznowienia (line 44): raz w InGracePeriod, czy ingestion jest jedynym pisarzem? Potwierdź że żaden krok nie może uruchomić się współbieżnie
  • Ponowne odrzucenie przestarzałej flagi zatrzymania (line 58): IsStopRequestedAsync wywoływane po walidacji — czy nakładanie się z równoczesnym zatrzymaniem od innego użytkownika stanowi rzeczywisty błąd czy łagodny podwójny sygnał?
  • Wyciek rezerwacji: line 69 komentarz mówi że rezerwacje trzymane przez pauzę, ale co jeśli cykl pauzy+restart powtarza się N razy? Potwierdź że TTL jest wystarczający i restart nie re-reservuje
11

Handlery zdarzeń

3 plików

Na co zwrócić uwagę: Rozpoczęcie potoku przy zdarzeniu queued integration event, sukces przy zdarzeniu order-completed oraz broadcast gotowego delivery. Sprawdź idempotentność i korelację.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/EventHandlers/BroadcastInboundDeliveryCompletedEventHandler.csniskie
Handler internal-event, który retransmituje sygnał completion delivery przez SignalR, aby tester / UI odświeżył się po zakończeniu converge.
  • Potwierdź, że broadcast wykonuje wyłącznie (brak zmutacji stanu) i jest bezpieczny do uruchomienia na każdym zdarzeniu delivery-completed (idempotentny).
  • Weryfikuj tolerancję dla deliveries tworzonych poza ingestion (nie zakładaj, że istnieje wiersz ingestion).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/EventHandlers/StartPipelineWhenIngestionQueuedEventHandler.csniskie
Uruchamia potok async ingestion przez enqueue pierwszy step po zatwierdzeniu transakcji
  • Potwierdź, że IntegrationEventHandler base gwarantuje post-commit execution (komentarz to potwierdza)
  • Weryfikuj, że pole Attempt skopiowane z event bez błędu off-by-one
  • Sprawdź, że Hangfire enqueue to fire-and-forget, nie blokuje ani nie rzuca wyjątku w przypadku niedostępności
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/EventHandlers/SucceedIngestionWhenGeneratedOrderCompletedEventHandler.csśrednie
Oznacza ingestion jako succeeded, gdy generated order się zakończy (IboGenerated na Succeeded transition)
  • Null ingestion return na linii 39-41 jest idempotentny, ale weryfikuj, że nie ma ciągnięcia utraty danych (order completed przed ingestion ready)
  • GetByGeneratedInboundOrderIdAsync musi używać unique index, aby zapobiec niedeterministycznemu wynikowi
  • Audit trail poprawnie wychwytuje userId i cross-context transition
12

Cron, zapytania i obsługi zapytań

9 plików1 wysokie

Na co zwrócić uwagę: Cron czyszczący nieużywane surowe ładunki (musi uwzględnić IboGenerated) oraz zapytania/obsługi po stronie odczytu.

src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/CronJobs/PurgeStaleIngestionRawPayloadsCronJob.csśrednie
Codzienny cron czyszczący usunięte/IboGenerated ingesty starsze niż liczba dni przechowywania oraz osierocone pliki tymczasowe starsze niż liczba godzin przechowywania.
  • Linia 55: `DateTime.UtcNow.AddDays(-retentionDays)` — sprawdzić, czy wszystkie znaczniki czasu ingestionu (Trace.Updated/Created) są gwarantowane w UTC, a nie lokalnie lub mieszane; off-by-one jeśli granica jest włączająca, a czyszczenie działa dokładnie o północy UTC.
  • Linia 60–65: `GetAllAsync()` filtruje wg statusu + RawPayloadPurged + znacznika czasu, następnie kolejkuje polecenie na wiersz — sprawdzić brak wyścigu, gdzie ingestion przechodzi do Succeeded między filtrem a kolejkowaniem, powodując zduplikowane polecenia czyszczenia.
  • Linia 69–74: `PurgeIngestionRawPayloadCommand` jest kolejkowany bez transakcji — jeśli stan ingestionu zmieni się przed wykonaniem polecenia, polecenie może próbować wyczyścić ingestion, który powinien być zatrzymany lub jest już wyczyszczony.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Query/GetCompletedInboundDeliveryIngestionsQuery.csniskie
Kontener DTO ze stronicowaniem: wykonuje zapytanie o ostatnio ukończone ingesty (powodzenie), zachowywane jako dziennik audytu z opcjonalnym pobieraniem surowego ładunku.
  • Linia 13: Domyślny `Take = 50` — rozsądny, ale sprawdzić, czy AppSettings nie zmienia tego na niebezpieczną wartość (powinien być ograniczony do 200).
  • Brak określonego porządku znaczników czasu w zapytaniu — potwierdzić, że obsługa sortuje po dacie utworzenia malejąco (najnowsze pierwsze) zgodnie z dokumentacją.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Query/GetExpandedInboundDeliveryIngestionPayloadQuery.csniskie
Kontener DTO: wykonuje zapytanie o rozwinięty ładunek ingestionu (przeanalizowany artefakt + wyekstrahowane pliki przebudowane jako multipart form), z fallbackiem wartości null na surowy ładunek.
  • IngestionId jest Guid, brak walidacji — potwierdź, że obsługa sprawdza wartość null i kaskadowo przechodzi do logiki surowego ładunku prawidłowo.
  • Dokumentacja mówi 'zwraca null gdy rozwinięty nie może być obsłużony w całości' — sprawdzić, czy obsługa rozróżnia między brakującym artefaktem a brakującym wyekstrahowanym plikiem (oba powinny zwracać null).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Query/GetInboundDeliveryIngestionQuery.csniskie
Prosty kontener DTO: wykonuje zapytanie o pojedynczy ingestion po ID, zwraca InboundDeliveryIngestionProgressResponse.
  • IngestionId jest Guid, ale brak walidacji — sprawdzić, czy wywoływana obsługa zwróci null bezpiecznie, jeśli ID to Guid.Empty lub format nieprawidłowy.
  • Brak stronicowania lub filtrowania — akceptowalne dla zapytania jednoelementowego, ale potwierdź, że klient wywołuje prawidłowo i nie masowo-wywołuje przez pętle.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/Query/GetInProgressInboundDeliveryIngestionsQuery.csniskie
Prosty kontener DTO: brak parametrów, wykonuje zapytanie o wszystkie ingesty w trakcie (Queued/Running/Failed) dla widoku listy magazynu.
  • Brak stronicowania — potwierdź, że repozytorium zwraca ograniczony zestaw wyników (sprawdzić kontrakt `GetInProgressWithStepsAsync`). Ryzyko OOM w przypadku tysięcy równoczesnych ingestionów.
  • Brak cache'u na poziomie zapytania — każde żądanie ponownie pobiera wszystkie ingesty w trakcie; może spowodować pik obciążenia DB podczas równoczesnych przesyłań.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/QueryHandlers/GetCompletedInboundDeliveryIngestionsQueryHandler.csniskie
Pobierz ostatnie ukończone ingesty, ogranicz rozmiar strony, zmapuj na DTO odpowiedzi.
  • Linia 20: `Math.Clamp(request.Take, 1, 200)` bezgłośnie zmienia wejście — potwierdź, że downstream validation nie nie powiedzie się (np. klient API oczekuje dokładnej liczby Take) lub logowanie klienta śledzi skok.
  • Linia 22: `GetRecentlyCompletedWithStepsAsync(take)` — sprawdzić, czy repozytorium wymusza porządek DESC tworzenia (najnowsze pierwsze), a nie asc.
  • Brak cache'u — każde wywołanie re-queryuje DB; dobrze dla zapytań audytu, ale potwierdź, że nie jest wywoływane w pętli przez UI.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/QueryHandlers/GetExpandedInboundDeliveryIngestionPayloadQueryHandler.cswysokie
Przebuduj rozwinięty ładunek ingestionu: załaduj przeanalizowany artefakt z magazynu, następnie streamuj wyekstrahowane pliki z PVC do wieloczęściowego body form-data. Null jeśli artefakt lub jakikolwiek plik brakuje, fallback do surowego przesyłu.
  • Linia 38–42: Sprawdzenie wartości null ingestionu — ale jeśli ingestion istnieje + artefakt usunięty przed uruchomieniem obsługi, obsługa zwraca null w linii 48 (poprawny fallback).
  • Linia 44–45: `LoadArtifactAsync()` — jeśli deserializacja nie powiedzie się, wyjątek propaguje (nie jest łapany). Sprawdzić, czy schemat artefaktu ma wersję i jest wstecz kompatybilny.
  • Linia 67: `storage.ReadExtractedFile()` zwraca null jeśli plik brakuje — ale w linii 73 `.OriginalFileName ?? .TempFileName` zakłada, że TempFileName istnieje; jeśli oba są null, NRE w linii 74. Sprawdzić, że ParsedFileRefArtifact gwarantuje non-null TempFileName.
  • Warunek wyścigu: wyekstrahowany plik usunięty przez cron job (PurgeOrphanTempFiles) między LoadArtifactAsync a pętlą WriteFiles — zwraca częściowy multipart + null w linii 98–111. Akceptowalny fallback, ale sprawdzić, że klient zamyka stream bezpiecznie.
  • Linia 91–93: Pola opcjonalne (ProductId, Note, ZipPassword) zapisywane tylko jeśli non-null/non-empty — sprawdzić, czy parser klienta obsługuje brakujące pola w multipart (powinien, ponieważ oryginalny formularz również może je pominąć).
  • Linia 51: Losowa wartość boundary — sprawdzić brak ryzyka kolizji (sufiks GUID powinien być wystarczający) i brak substring boundary w zawartości pliku.
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/QueryHandlers/GetInboundDeliveryIngestionQueryHandler.csśrednie
Pobierz jeden ingestion: cache-first (via progressService), fallback do DB + zmapuj na DTO postępu. Używane przez klienta SignalR i UI dla stanu rzeczywistego czasu.
  • Linia 20–24: `TryGetAsync()` zwraca cache'owany postęp jeśli obecny — sprawdzić, czy klucz cache'u to tylko IngestionId (brak zakresu numeru próby); nieaktualny jeśli próba zmieni się w środku grace bez unieważnienia cache'u.
  • Linia 26: `GetWithStepsAsync()` pobiera ingestion + wszystkie kroki — sprawdzić brak N+1 jeśli kolekcja steps jest duża (sprawdzić plan zapytania EF).
  • Linia 28: Zwraca null jeśli ingestion nie znaleziony — poprawne, ale sprawdzić, że kontroler API zwraca 404 (nie 200 z null body).
src/WOCK.WholeSale.Application/Deliveries/InboundDeliveryIngestion/QueryHandlers/GetInProgressInboundDeliveryIngestionsQueryHandler.csśrednie
Pobierz wszystkie ingesty w trakcie, następnie sprawdzenie cache'u flagi stop-requested dla każdego wiersza; używane przez odświeżanie listy magazynu.
  • Linia 22: `GetInProgressWithStepsAsync()` pobiera listę DB raz — sprawdzić, czy repozytorium filtruje wg statusu (Queued/Running/Failed) i zwraca ograniczony wynik.
  • Linia 29–34: Per-response, wzywaj `IsStopRequestedAsync()` (tylko cache) dla Queued/Running — jeśli sprawdzenie cache'u jest powolne lub blokujące, skaluje się O(N) z równoczesnymi ingestionami; potencjał do spowolnienia odświeżania listy.
  • Warunek wyścigu: ingestion przechodzi do Succeeded po pobraniu linii 22, ale przed sprawdzeniem cache'u linii 33 — odpowiedź pokaże niezgodność statusu (np. Queued w DB, ale StopRequested=true w cache'u). Zwierciadło sprawdzenia cache'u przed zwróceniem.
13

Usługi i fabryki

5 plików

Na co zwrócić uwagę: Konkretne implementacje: magazyn PVC, ekspander ZIP, rezerwacja kluczy Redis (Lua), usługa postępu, lustro kluczy.

src/WOCK.WholeSale.Factories/Ingestion/InboundDeliveryIngestionStorage.csśrednie
Magazyn artefaktów pozyskiwania kopii zapasowej PVC z blokowaniem Redis na poziomie pozyskiwania dla rezerwacji przesyłania współbieżnego; ładunki pierwotne i artefakty numerowane próbą.
  • Sprawdź, czy limit czasu blokady Redis degraduje bezpiecznie: czy optymistyczny token współbieżności na agregacie rzeczywiście zapobiega duplikatowaniu próby podczas awarii Redis (nie tylko minimalizuje okno kolizji)?
  • Sprawdź: jeśli CopyToAsync() rzuci błąd w trakcie ciała po utworzeniu folderu, czy sierota attempt-{n}/ folder jest czyszczony lub czy blokuje przyszłe próby poprzez licznik NextAttempt()?
src/WOCK.WholeSale.Factories/Ingestion/GeneratedInboundOrderSink.csniskie
Trywialny scoped holder implementujący IGeneratedInboundOrderSink (IService → scoped).
  • Potwierdź, że trzyma tylko jedną zapisaną wartość i dostaje świeżą instancję na scope komendy.
src/WOCK.WholeSale.Factories/Ingestion/InboundDeliveryZipExpander.csniskie
Analizuje i rozpakowuje archiwa ZIP na linię bazową + zależne podlinie; oddziela wpisy zaszyfrowane, pomija zagnieżdżanie >2 poziomów.
  • Potwierdź: każda unikalna nazwa folderu generuje świeży RequestId (linia 142). Czy jest to zamierzone czy powinny nazwy duplikatów folderów ponownie używać tej samej podlinii?
  • Sprawdź czyszczenie pliku tymczasowego w wyjątku ExtractEntry(): jeśli entry.Extract() nie powiedzie się, ale plik jest już utworzony, czy jest pozostawiony?
src/WOCK.WholeSale.Factories/Ingestion/IngestionKeyReservationService.csniskie
Atomowa rezerwacja oparta na Redis za pośrednictwem skryptu Lua; chroni przed współbieżnymi roszczeniami dzięki czyszczeniu zestawu posiadanego na pozyskiwanie.
  • Sprawdź, czy indeksowanie tablicy Lua wyrównuje się z budową argumentów C# (linie 71-84): KEYS[0]=owned-set, KEYS[1..]=keys; ARGV[0]=ingestionId, ARGV[1]=ttl, ARGV[2..]=hashes. Skrypt oczekuje ARGV[1]=owner, ARGV[2]=ttl.
  • Potwierdź: jeśli Distinct() (linia 65) usuwa duplikatowe wejścia skrótu, czy kod wywołujący oczekuje tego zachowania czy powinny duplikaty zwracać błąd?
src/WOCK.WholeSale.Factories/Ingestion/IngestionProgressService.csniskie
Migawka postępu wspierana pamięcią podręczną z transmisją SignalR; stan uruchomienia bez DB + flaga zatrzymania; wtrysk syntetycznego kroku dla interfejsu użytkownika na żywo.
  • Sprawdź: syntetyczny krok uruchomienia (linie 109-120) używa DateTime.UtcNow dla StartedAtUtc. Jeśli PublishStepProgressAsync() jest wywoływany wiele razy na krok, czy tworzy to wiele wpisów czy aktualizuje jeden?
  • Sprawdź cykl życia flagi zatrzymania: TTL 6h. Jeśli krok trwa 5,9h + 1m, czy flaga zatrzymania wygasa w trakcie wykonania? Czy jest to akceptowalne czy TTL powinno być świadome czasu trwania kroku?
src/WOCK.WholeSale.Factories/Ingestion/KeysStorageMirror.csniskie
Opcjonalne lustro PVC drugorzędne dla kluczy pliku blob; bez operacji, jeśli nie skonfigurowane; zwykła kopia pliku.
  • Sprawdź: nazwa pliku blob (parametr blobFileName) jest oczyszczana przed połączeniem ze ścieżką prefiksu (linia 31)—czy jest możliwe przechodzenie przez katalogi (../)?
  • Potwierdź: jeśli CopyToAsync() nie powiedzie się, czy pozyskiwanie nie powiedzie się czy milcząco kontynuuje z lustrem częściowym? Czy jest to zamierzone?
14

Framework i usługi wspóldzielone

6 plików

Na co zwrócić uwagę: Dodania SignalR broadcast, interfejs cache, nowe pola AppSettings dla ingestion i mock'i testowe. Recenzja tylko różnic.

src/WOCK.Framework.Cache/ICacheService.csniskie
Dokumentacja interfejsu abstrakcji cache — brak zmian logiki
  • Docstring na Remove() wyjaśnia semantykę no-op gdy brakuje klucza — defensywne; pokrywa się z oczekiwaniami callersów w IngestionProgressService.PublishStopRequestedAsync i RemoveAsync
  • Weryfikuje callersów cache (IngestionProgressService linie 74, 80, 86, 94, 97) aby nie zakładali wyjątku przy braku klucza
src/WOCK.Framework.Utils/Mocks/BackgroundServiceMock.csniskie
Infrastruktura testowa: kolejka poleceń record-only dla deterministycznego wykonania async pipeline
  • EnqueuedCommands Queue<IBackgroundCommand>: przechwytuje Enqueue() bez uruchamiania; ClearEnqueuedCommands() resetuje między testami
  • Enqueue(IBackgroundCommand) override (linie 52-55) pasuje do sygnatury bazowej i kolejkuje synchronicznie — brak race condition w dispatchu
  • Wzorzec testów integracyjnych: opróżnianie kolejki via MediatR.Send(odkolejkowanych poleceń) po zacommitu transakcji, unikając podatnych na flakiness async waitów; docstring wyjaśnia intencję jasno
  • Izolacja testów: kolejka żyje per test (wyczyszczana przez ClearEnqueuedCommands), brak wycieków między testami
src/WOCK.Framework.Utils/Mocks/NotificationServiceMock.csniskie
Infrastruktura testowa: record-and-verify broadcast notifications dla asercji w testach
  • Broadcasts List<(string Method, object Payload)> przechwytuje wszystkie BroadcastToAll() calls; no-op in-memory storage
  • BroadcastToAll() (linie 18-23) pasuje do interfejsu dokładnie; Task.CompletedTask umożliwia await bez blokowania
  • Używane przez IngestionProgressServiceTests aby weryfikować progress updates publikują się prawidłowo; brak race conditions (append do listy jest atomowe per entry)
src/WOCK.Framework.WebAPI.Abstraction/Models/AppSettings.csniskie
Centralny model konfiguracji dla limitów ingestion, retencji i routingu storage
  • 4 nowe właściwości int/string wszystkie inicjalizowane defaultowo (0 lub null): KeysStoragePrefix, IngestionStepDelaySeconds, IngestionRawPayloadRetentionDays, IngestionMaxUploadMegabytes; defaults są defensywne (pomijają opcjonalne features)
  • KeysStoragePrefix: używane przez KeysStorageMirror.IsConfigured (check: !string.IsNullOrWhiteSpace) — poprawnie bramkowane; brak wyjątku jeśli brak ustawienia
  • IngestionMaxUploadMegabytes: kontroler ValidateUploadSize() sprawdza <=0 jako sygnał brak-limitu — poprawne (linia w InboundDeliveryIngestionsController: 'if (appSettings.IngestionMaxUploadMegabytes <= 0) return null')
  • IngestionRawPayloadRetentionDays: cron job PurgeStaleIngestionRawPayloadsCronJobHandler sprawdza >0 przed purging — bezpieczne default-retain-forever
  • IngestionOrphanFileRetentionHours: sparowany z RetentionDays, ten sam wzorzec >0 gating
src/WOCK.WholeSale.Services/Websocket/NotificationService/INotificationService.csniskie
Kontrakt interfejsu dla dwukierunkowego dostarczania notyfikacji (użytkownik-kierowany + broadcast)
  • BroadcastToAll(string method, object payload) sygnatura pasuje do implementacji (NotificationService.cs linie 22-25) i wszystkich callsites (IngestionProgressService linie 76, 98; BroadcastInboundDeliveryCompletedEventHandler linia 22)
  • Typ zwrotu async Task konsystentny z SendNotification; awaitable na callsites
  • Dokumentacja metody wyjaśnia oczekiwane struktury payload dla klientów
src/WOCK.WholeSale.Services/Websocket/NotificationService/NotificationService.csniskie
Wrapper SignalR hub delegujący do IHubContext<WholeSaleHub>.Clients.All.SendAsync
  • BroadcastToAll deleguje bezpośrednio do hub: _notificationHub.Clients.All.SendAsync(method, payload) — brak serializacji/filtrowania; payload to POCO
  • Brak null-checkó na method lub payload — zakłada walidację callera; pasuje do wzorca SendNotification
  • Współbieżność: IHubContext SignalR jest thread-safe (stateless), wielokrotne concurrent broadcasts są bezpieczne
15

WebAPI i infrastruktura

7 plików3 wysokie

Na co zwrócić uwagę: Kontroler, parser multipart ze strumieniowaniem, filtr Hangfire dla błędów terminalnych, dynamiczne ładowanie konfiguracji oraz wiring DI w Startup. Potwierdź, że wszystko jest zarejestrowane.

src/WOCK.WholeSale.WebAPI/Controllers/Deliveries/InboundDeliveryIngestionsController.cswysokie
Powierzchnia HTTP API dla asynchronicznego poboru dostarczeń inbound: obsługuje przesyłania, przejścia stanów (stop/start/resume/restart/delete) oraz punkty końcowe pobierania audytu (surowe i rozwinięte ładunki).
  • DownloadPayload & DownloadExpandedPayload: weryfikuj, że zwrócony strumień jest prawidłowo zwalniany — metoda File() musi gwarantować czyszczenie przy ukończeniu lub błędzie; jeśli menedżer magazynu zwraca niezamknięte strumienie, dodaj using(). Potwierdź, że nie ma warunku wyścigu pomiędzy asynchronicznym zapytaniem a dostępnością strumienia pliku.
  • GetCompleted: parametr take jest ograniczony do 50 jeśli ≤0, ale nie ma górnego ograniczenia — dodaj limit (np. take = Math.Min(take, 1000)) aby uniknąć DoS.
  • Restart/Start/Resume/Delete: komendy weryfikują stan poboru (np. 'not in Failed state' dla restart) — potwierdź, że wyjątek wraca do klienta jako 400 BadRequest, nie 500; sprawdź, czy handler komendy rzuca DomainValidationException czy wyjątek custom.
src/WOCK.WholeSale.WebAPI/Infrastructure/Configuration/IngestionRuntimeConfigReloader.csniskie
IHostedService mostkujący przeładowywalny IOptionsMonitor na singleton AppSettings, propaguje zmiany ConfigMap bez restartu poda
  • Handler OnChange na linii 37 wyzwala się na reload ConfigMap — sprawdzenie równości na linii 50 zapobiega fałszywym aktualizacjom i logowaniu przy braku zmian
  • Bezpośrednie przypisanie do singletona AppSettings na linii 58 jest atomowe dla int (IngestionStepDelaySeconds) — bezpieczne równoczesne odczytywanie przez handlery kroków
  • Czyszczenie subskrypcji na linii 43 podczas StopAsync — zapobiega wiszącym odwołaniom gdy reloader jest usuwany
src/WOCK.WholeSale.WebAPI/Infrastructure/Configuration/IngestionRuntimeOptions.csniskie
Configuration POCO dla tunowanych w runtime ustawień poboru, wiązany do sekcji AppSettings z włączonym reloadOnChange
  • Pojedyncza właściwość IngestionStepDelaySeconds na linii 12 pasuje do nazwy właściwości AppSettings — kontrakt wiązania jest stabilny
  • Projekt klasy wspiera rozszerzenie (komentarz na linii 7 mówi 'add more runtime-tunable fields here as needed') — rozszerzalny bez zmian pipeline
src/WOCK.WholeSale.WebAPI/Infrastructure/Filters/IngestionStepTerminalFailureFilter.csniskie
Filtr zadania Hangfire: oznacza pobór jako błąd terminalny gdy krok pipeline wyczerpie ponowienia (błędy transient/infra), zapobiega zawieszeniu się Running
  • Warunek RetryCount==MaxRetries (10) na linii 33 zapewnia, że filtr działa tylko na ostateczny permanent failure, nie na ponowienia pośrednie — zapobiega false positives
  • Ekstraktuje InboundDeliveryIngestionStepCommand via OfType na linii 39 — bezpieczna kontrola null na linii 42 pomija zadania które nie przenoszą kontekstu kroku
  • Enqueue MarkIngestionTerminallyFailedCommand na linii 47 przekazuje IngestionId+Attempt+UserId — umożliwia handlerowi pominięcie stałych błędów z superseded attempts
src/WOCK.WholeSale.WebAPI/Infrastructure/Ingestion/InboundDeliveryMultipartParser.csśrednie
Parser multipart form ze strumieniowaniem: deserializuje przechowywane surowe ciało do strukturyzowanej komendy, zapisuje przesłane pliki do PVC, pozostawia ZIPy dla kroku rozpakowania
  • Zarządzanie cyklem życia strumienia pliku: tworzenie na partNumber==0 (linia 158), ponowne użycie na kolejnych chunkach (linia 168), prawidłowy DisposeAsync w pętli (linia 195) — zapobiega wyciekom uchwytów plików
  • Parsowanie pola multipart używa stałych InboundDeliveryUploadFields (linie 31–41) — pojedyncze źródło prawdy zapobiega cichu dryfować z writer rozszerzonego ładunku
  • Indeksowanie pliku via fileKey=[baseIndex,subIndex,fileIndex] tuple (linia 153) prawidłowo kieruje chunki do właściwego pliku i struktury komendy (linie 174–188)
src/WOCK.WholeSale.WebAPI/Program.cswysokie
Setup ładowania konfiguracji z hot-reload support dla mutacji Kubernetes ConfigMap i polling file watcher.
  • Linie 25-34: Setup zmiennej env DOTNET_USE_POLLING_FILE_WATCHER przed build hosta - weryfikuj, że polling watcher jest ustawiony tylko jeśli ustawiony (idempotentny). Kubernetes ConfigMap symlink swaps wymagają polling; FileSystemWatcher może je zmarnować. Krytyczne dla niezawodnego reloadOnChange.
  • Linie 65-74: Wszystkie cztery źródła appsettings.json teraz mają reloadOnChange: true (shared + environment-specific + published + ConfigMap overrides). Weryfikuj porządek: shared base -> env-specific override -> published -> ConfigMap hot-reload. Ścieżka ConfigMap musi być zamontowana na {ContentRoot}/config/appsettings.overrides.json w manifestach Kubernetes.
  • Linie 73-74: Opcjonalny plik overrides ConfigMap + hot-reload - weryfikuj, że pod Kubernetes spec montuje ConfigMap jako volume (nie subPath) aby zachować zachowanie symlink. Jeśli brakuje, plik nie istnieje i optional: true pozwala na startup. IngestionRuntimeConfigReloader mostkuje zmiany do singletona AppSettings.
src/WOCK.WholeSale.WebAPI/Startup.cswysokie
Wiring dependency injection i usług dla runtime poboru, timeoutów komend, filtrów Hangfire oraz warunkowego Azure SignalR.
  • Linie 133-139: Wiązanie konfiguracji IngestionRuntimeOptions do sekcji AppSettings + hosted service IngestionRuntimeConfigReloader - weryfikuj, że łańcuch rozwiązywania DI działa (IOptionsMonitor -> reloader -> AppSettings singleton). AppSettings musi mieć właściwość IngestionStepDelaySeconds (potwierdzone w Framework.WebAPI.Abstraction).
  • Linia 208: Rejestracja ICommandTimeout<CreateInboundDeliveryStepCommand> - weryfikuj, że ten timeout (900s) pasuje do profilu obciążenia kroku (converge zapisuje delivery + przesyła klucze w jednej transakcji). Odbija linię 207 dla flow synchronicznego.
  • Linie 405-412: Warunkowe wiring Azure SignalR - weryfikuj, że sekcja konfiguracji 'Azure:SignalR:ConnectionString' pasuje do porządku ładowania konfiguracji w Program.cs. Lokalna dev bez Azure MSI może teraz działać bez połączenia SignalR; prod/QA wykorzystuje usługę Azure. Brak warunku wyścigu ponieważ builder usługi jest przypisany przed warunkiem.
16

Persistencja i konfiguracja EF

3 plików

Na co zwrócić uwagę: Konfiguracje EF Core i repozytorium. Sprawdź typy kolumn i ich długości, owned Trace, oraz eager-load includes.

src/WOCK.WholeSale.Persistence/Deliveries/Configuration/InboundDeliveryIngestionConfiguration.csniskie
Konfiguracja aggregate root EF—podłącza kolekcję dzieci (steps), ignoruje pola przejściowe, stosuje audit trait.
  • Steps HasMany z Cascade delete (linie 17-20)—zgadza się z ograniczeniem FK w migracji
  • Status/CurrentStep/Origin ignored (linie 25-27)—wyliczane z właściwości *Code
  • DeliveryCompletionScheduledAtUtc/DeliveryGracePeriodSeconds ignored (linie 30-31)—pola broadcast-only tylko przejściowe
  • Wywołanie HasAudit() (linia 33) auto-konfiguruje Trace (OwnsOne) + Audits (OwnsMany) via TraceableConfiguration generic + HasAudit extension
src/WOCK.WholeSale.Persistence/Deliveries/Configuration/IngestionStepExecutionConfiguration.csniskie
Konfiguracja EF Core encji podrzędnej—wiersze audytu wykonania kroku w agregacie ingestion.
  • ValueGeneratedNever na Id używa SequentialGuidGenerator zgodnie z modelem domeny (linie 12-13)
  • Index na (Attempt, StepTypeCode) poprawnie wdrożony dla lookupów retry kroków (linia 15)
  • ErrorPayload HasMaxLength(int.MaxValue) zgadza się ze schematem nvarchar(max) (linia 19)
  • StepType, StepStatus ignored poprawnie; brak Trace/Audits (encja nie implementuje ITraceable, linie 21-22)
src/WOCK.WholeSale.Persistence/Deliveries/Repositories/InboundDeliveryIngestionsRepository.csśrednie
Warstwa dostępu do danych dla InboundDeliveryIngestion: eager-loads Steps i Trace.Creator; obsługuje zapytania po id/order-id, filtrowanie in-progress po status code, paginację niedawno zakończonych.
  • GetInProgressWithStepsAsync: upewniaj się, że IngestionStatus.InProgressCodes nigdy nie jest puste/null i zawiera prawidłowe kody statusu—dodaj asercję defensywną lub test jednostkowy.
  • GetRecentlyCompletedWithStepsAsync: sprawdź, czy .Take() pojawia się *po* wszystkich .Include() wywołaniach w łańcuchu LINQ (obecny porządek łańcuchuje includes po take, co może powodować N+1).
  • Potwierdź, że wszystkie zapytania zwracają w pełni nawodnione agregaty do ewaluacji business rules—ładowanie Trace.Creator zapewnia, że snapshoty SignalR zawierają nazwę autora bez migotania.
17

Migracje EF

9 plików

Na co zwrócić uwagę: Weryfikacja zgodności migracji z konfiguracjami oraz że są tylko w kierunku do przodu. Skupić się na metodzie Up(); pliki *.Designer / snapshot są generowane przez EF.

src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260530044305_AddedIngestion_Deliveries.csśrednie
Inicjalna definicja schematu—trzy tabele ingestion (główny agregat root + audit + jednostka potomna step).
  • PK i trzy FK do User(settings.User) wszystkie Restrict przy usunięciu; InboundDeliveryIngestionAudits→InboundDeliveryIngestion Cascade (linie 36-107)
  • IngestionStepExecution FK do InboundDeliveryIngestion Cascade zapewnia wyczyszczenie (linie 102-107)
  • InboundDeliveryIngestion.StatusCode indeksowany (linie 117-120); IngestionStepExecution (Attempt, StepTypeCode) indeksowany (linie 141-144)
  • Kolumny Trace (Created, CreatedBy, Updated, UpdatedBy) wbudowane w tabelę InboundDeliveryIngestion zgodnie z konwencją (linie 30-33); tabela Audits oddzielna
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260530044305_AddedIngestion_Deliveries.Designer.csniskie
Migacja snapshot generowany przez EF (Designer) — odzwierciedla stan modelu w tej migracji; nie pisany ręcznie.
  • Wygenerowany przez EF; weryfikacja czy regeneruje się czyszczeniu i czy pasuje do konfiguracji jednostek — nie ma potrzeby przeglądu linii po linii tekstu.
  • Potwierdzenie że pasuje do jego migracji `.cs`.
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260611133321_IngestionSimplify_Deliveries.csniskie
Uproszczenie—usunięte ContentType i OriginUserId; zastąpiony ten drugi enum OriginCode.
  • Usunięcie ContentType jest bezpieczne (nieużywane zgodnie z komentarzami domeny)
  • Usunięcie OriginUserId jest bezpieczne (OriginCode dodane w następnej migracji); Down() poprawnie przywraca domyślne wartości
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260611133321_IngestionSimplify_Deliveries.Designer.csniskie
Migacja snapshot generowany przez EF (Designer) — odzwierciedla stan modelu w tej migracji; nie pisany ręcznie.
  • Wygenerowany przez EF; weryfikacja czy regeneruje się czyszczeniu i czy pasuje do konfiguracji jednostek — nie ma potrzeby przeglądu linii po linii tekstu.
  • Potwierdzenie że pasuje do jego migracji `.cs`.
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260616063223_IngestionAdjustments_Deliveries.csniskie
Trwałość stanu okresu łaski—cztery nowe kolumny nullable dla logiki pauzy/wznowienia.
  • GracePeriodSeconds, GracePeriodStartedAtUtc, GraceToken nullable (linie 14-33)
  • OriginCode NOT NULL z defaultValue: 0 (linie 35-41)—bezpieczne, ale weryfikacja czy kod domeny obsługuje domyślnie zero
  • Logika Down() drop/restore poprawna; brak straty danych przy cofnięciu
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260616063223_IngestionAdjustments_Deliveries.Designer.csniskie
Migacja snapshot generowany przez EF (Designer) — odzwierciedla stan modelu w tej migracji; nie pisany ręcznie.
  • Wygenerowany przez EF; weryfikacja czy regeneruje się czyszczeniu i czy pasuje do konfiguracji jednostek — nie ma potrzeby przeglądu linii po linii tekstu.
  • Potwierdzenie że pasuje do jego migracji `.cs`.
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260619071643_IngestionDeliveryAndOrderReferences_Deliveries.csniskie
Przechwycenie referencji po ingestion—dodane trzy kolumny do zapisywania wygenerowanych ID dostawy/zamówienia.
  • CreatedInboundDeliveryReference, GeneratedInboundOrderId, GeneratedInboundOrderReference wszystkie nullable (linie 14-35)
  • Wszystkie nvarchar(250) pasują do właściwości string w domenie (linie 19, 33)
  • Down() bezpiecznie usuwa wszystkie trzy kolumny
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/20260619071643_IngestionDeliveryAndOrderReferences_Deliveries.Designer.csniskie
Migacja snapshot generowany przez EF (Designer) — odzwierciedla stan modelu w tej migracji; nie pisany ręcznie.
  • Wygenerowany przez EF; weryfikacja czy regeneruje się czyszczeniu i czy pasuje do konfiguracji jednostek — nie ma potrzeby przeglądu linii po linii tekstu.
  • Potwierdzenie że pasuje do jego migracji `.cs`.
src/WOCK.WholeSale.Persistence.Migrations/Deliveries/DeliveriesContextModelSnapshot.csśrednie
Snapshot Fluent API—zapisuje stan schematu ingestion po czterech migracjach.
  • InboundDeliveryIngestion: Trace OwnsOne (FK do User CreatedBy/UpdatedBy oba Restrict, Trace.IsRequired, linia 2070-2099); Audits OwnsMany tabela InboundDeliveryIngestionAudits (FK CreatedBy Restrict, linie 2101-2147)
  • IngestionStepExecution: brak Trace/Audits; FK do InboundDeliveryIngestion Cascade (linie 2183-2186)
  • Indeks na InboundDeliveryIngestion.StatusCode obecny (snapshot linie 865); Indeks na IngestionStepExecution (Attempt, StepTypeCode) i InboundDeliveryIngestionId obecny (snapshot linie 2187-2190)
18

Testy

20 plików9 wysokie

Na co zwrócić uwagę: Pokrycie agregatu, reguł, cyklu życia podstawowego, każdego handlera i testu integracyjnego. Sprawdź, czy rzeczywiście asertuje się ścieżki obarczone ryzykiem (token grace, stop/resume, converge).

src/WOCK.WholeSale.Tests.Integration/Deliveries/Inbound/InboundDeliveryIngestionIntegrationTests.cswysokie
Orchestracja end-to-end: tworzenie dostawy w scenariuszu happy-path, tryby awarii dla każdego kroku, ochrona przed konkurencją, cykl życia stop/start/resume/delete, i pokrycie zapytań.
  • Konkurencyjne zarezerwowanie kluczy prawidłowo blokuje krok Reserve i kończy się właściwym kodem błędu
  • Komendy Start/Stop/Resume wykonują oczekiwane przechodzenia stanów; pipeline zbiegają się bez pętli (max 200 kroków)
  • Faza grace (jeśli testowana) prawidłowo planuje opóźniony converge i wspiera pauzę/wznowienie bez utraty kluczy
  • Wszystkie endpointy zapytań (szczegóły, lista, ukończone, ładunek) zwracają 200 OK po pomyślnej ingestionie
src/WOCK.WholeSale.Tests.Integration/Helpers/EndpointsPaths.csniskie
Stała routingu testu dla sub-endpointu ingestionów
  • InboundDeliveryIngestionsPath = '/DeliveriesManagement/Inbounds/Ingestions' — odbija routing InboundDeliveriesController; umożliwia klientom testowym kierowanie na endpoint uploadowania
  • Stała ciągu niezmiennika; brak obaw dotyczących sekretów lub środowisk cross-environment
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/AwaitGracePeriodStepHandlerTests.cswysokie
Wejście do okna grace'u i planowanie opóźnienia converge'u: dopasowanie tokenu grace'u, rozróżnienie zaplanowane vs. enqueue'owane, i prawidłowe ukończenie kroku.
  • Token grace'u osadzony w CreateInboundDeliveryStepCommand zgadza się z GraceToken w ingestionie
  • Converge jest planowany (opóźniony), nie enqueue'owany (natychmiastowy) — weryfikuj brak natychmiastowej następnej komendy
  • Krok AwaitGracePeriod jest zapisany jako Succeeded, pozostawiając ingestion w stanie InGracePeriod
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/CreateInboundDeliveryIngestionCommandHandlerTests.csniskie
Początkowe tworzenie ingestionu: instancjacja agregatu z ursprungiem WholeSale i trwałością linii bazowej.
  • Ingestion utworzony z prawidłowym kodem pochodzenia (WholeSaleCode, nie null/Acquisition)
  • Agregat dodany do repo i SaveChangesAsync wywoływany dokładnie raz
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/DetectDuplicatesStepHandlerTests.cswysokie
Detekcja duplikatów w ładunku: ten sam klucz w liniach, zwalnianie zarezerwowania przy awarii, zaawansowanie łańcucha przy powodzeniu.
  • Duplikat w ładunku (w obrębie lub pomiędzy liniami) kończy się niepowodzeniem i zwalnia zarezerwowanie synchronicznie
  • Czysty ładunek utrzymuje zarezerwowanie (nie zwalnia), umożliwiając następnemu krokowi przesłanie kluczy dla utrzymywanego zestawu
  • Awaria nie przesuwa łańcucha; powodzenie przesuwa do UploadKeys
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/ExpandArchivesStepHandlerTests.csśrednie
Krok rozpakowania archiwum: detekcja, pomyślne rozszerzenie, błędy uszkodzenia/szyfrowania/hasła, i łagodne pominięcie na nie-archiwum.
  • Puste archiwum (brak użytecznych plików) zawodzi z błędem biznesowym przed przesunięciem łańcucha
  • Uszkodzone archiwum i przypadki błędnego hasła — ładunek błędu zawiera określone kody (archive_password_protected, archive_wrong_password)
  • Plik nie-archiwum pomija się łagodnie bez aktualizacji magazynu artefaktów, nadal przesunięcie do następnego kroku
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/GetExpandedInboundDeliveryIngestionPayloadQueryHandlerTests.csniskie
Rekonstrukcja wieloczęściowego pliku wyodrębnionego: powrót do raw w razie brakujących plików, zagnieżdżenie hierarchii zależności, i serializacja załącznika MIME.
  • Brakujący plik wyodrębnionych (usunięty z magazynu temp) zwraca null, wyzwalając fallback narzędzia do surowego multiparta
  • Multipart odbudowuje hierarchię partnera/linii/podlinii z poprawnymi nazwami pól formy dla powiązania API
  • Załączniki pliku noszą nazwę oryginalnego pliku i typ MIME
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/InboundDeliveryIngestionStepHandlerTests.cswysokie
Kontrakt handlera kroku podstawowego: upuszczenie zebranego starszego krokku, ponowna próba na nowszym kroku, zatrzymanie flagi, wyniki pominięte/powodzenie/awaria biznesowa, i idempotentne przyspieszenie.
  • Zebrany starszy krok (krok 1 po restarcie do 2) cicho opada bez dotykania DB — brak osieroconych wierszy kroków
  • Nowszy krok nie jeszcze widoczny (READ_COMMITTED_SNAPSHOT) rzuca (nie opada) aby Hangfire ponawiał — krytyczne dla bezpieczeństwa pierwszego kroku restartu
  • Wynik NoOp rejestruje zerowe wiersze kroków, aby uniknąć fałszywego skrócenia żywego zadania; Wyjątek przejściowy pozostawia ingestion niezmieniony (brak przedwczesnego przerzutu Failed)
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/InboundDeliveryIngestionTests.cswysokie
Maszyna stanów agregatu domeny: tworzenie, przechodzenia kroków, wejście w fazę grace/pauza/wznowienie, ochrony stanu terminalnego, i idempotentność ukończenia kroku.
  • Grace-pauza i wznowienie używają wyraźnych tokenów — weryfikuj ponowne enqueue'owanie starej pauzy nie podwaja convergence
  • RecordStepSucceeded w stanie terminalnym nigdy nie obniża — sprawdź zaawansowany CreateInboundDelivery po converge'cie bez przerzutu
  • ResumeGracePeriod odrzuca ingestion'y bez pauzy i utrzymuje numer kroku (brak syntetycznego restartu)
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/CreateInboundDeliveryStepHandlerTests.csniskie
Testy jednostkowe inline'owego zapisu IBO w converge: wygenerowane zamówienie → IBO wygenerowane; brak → Succeeded.
  • Potwierdź, że obie gałęzie sprawdzają status + GeneratedInboundOrderId oraz że nie kolejkuje się żadnej komendy follow-up.
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/IngestionProgressServiceTests.csniskie
Cache flagi zatrzymania i broadcast postępu: cykl życia cache'u, wstrzyknięcie syntetycznego kroku Running, i semantyka push SignalR.
  • Flaga zatrzymania round-trip'y: żądanie → sprawdzenie → czyszczenie, wszystko wspierane cache'em asynchronicznym
  • PublishRunning wstrzykuje syntetyczny krok Running (nie trwały), używany do postępu UI w czasie rzeczywistym
  • Remove czyści cache i broadcastuje zdarzenie usunięcia z odrębną nazwą metody
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/IngestionRulesTests.csśrednie
Egzekwowanie reguł biznesowych: sprawdzanie bramki stanów (restart, stop, wznowienie, start, usunięcie) obejmujące wszystkie kody stanów i przypadki brzegowe.
  • IboGeneratedCode blokuje restart (dostawa już utworzona) i stop (post-converge) — niezmienny po utworzeniu dostawy
  • Wznowienie akceptuje tylko Stopped + IsPausedInGracePeriod, nie bare Stopped (chroni niegraceowe ścieżki pauzy)
  • Porządek pipelinu (Reserve przed Duplicate, Upload przed Create) jest zakodowany i weryfikowany
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/IngestionStepFailureTests.csniskie
Akumulacja i serializacja błędów: streszczanie kategorii, kształt JSON ładunku błędu, i semantyka line-request-id.
  • JSON ładunku błędu jest camelCase z polem summary streszczającym liczbę i rozkład kategorii
  • Błędy na poziomie dostawy mają null LineRequestId; błędy na poziomie linii noszą RequestId
  • Pusta awaria nie ma streszczenia i IsFailed=false
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/MarkIngestionTerminallyFailedCommandHandlerTests.csśrednie
Haczyk awarii terminalnej: ochrona przed zastępowaniem kroku, idempotentność stanu terminalnego, i łaskawość dla brakującego ingestion'u.
  • Zastępowany krok (nowszy krok istnieje) jest cicho opuszczany
  • Już terminalny ingestion (Succeeded) jest idempotentnym no-op
  • Brakujący ingestion jest ignorowany łaskawię (bez wyjątku) — asynchroniczne bezpieczeństwo fire-and-forget
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/ReserveKeysStepHandlerTests.cswysokie
Ochrona zarezerwowania klucza: detekcja konfliktu konkurencyjnego ingestion'u poprzez usługę zarezerwowania, przepływ awarii bez zaawansowania.
  • Zarezerwowany klucz z innego ingestion'u zawodzi z prawidłowym błędem (key_concurrently_ingested implikuje)
  • Brak konfliktu zarezerwowania pozwala zaawansować do DetectDuplicates; zarezerwowanie nie jest zwalniane przy powodzeniu (utrzymywane do po uploadzieie)
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/ResumeInboundDeliveryIngestionCommandHandlerTests.cswysokie
Wznowienie pauzy grace'u: ponowne uzbrojenie grace'u z nowym tokenem, ponowne użycie tego samego kroku, planowany converge, i walidacja dla stanu grace-pauzowanego.
  • Wznowienie odrzuca Stopped bez grace (nie powinno być wywoływane na wczesnych ścieżkach stop) z DomainValidationException
  • Nowy token grace'u jest generowany i osadzony w zaplanowanej komendzie CreateInboundDelivery
  • Krok pozostaje ten sam (ponowne użycie przygotowanych kluczy); converge zaplanowany (nie enqueue'owany) na opóźnieniu takim samym jak oryginał
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/StartInboundDeliveryIngestionCommandHandlerTests.csśrednie
Wznowienie od Stopped (nie grace): walidacja, ponowne użycie tego samego kroku, i odrzucenie pauzy grace'u, aby wymusić ścieżkę Resume.
  • Ingestion pauzowany grace'em (Stopped ale IsPausedInGracePeriod=true) jest odrzucany — musi być użyta Resume zamiast
  • Stopped bez grace'u jest dozwolony, zachowuje numer kroku (ponowne użycie przygotowanych kluczy), i czyści flagę stop
  • Zapisuje raz i broadcastuje zmianę stanu
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/StopInboundDeliveryIngestionCommandHandlerTests.cswysokie
Obsługa żądania zatrzymania: synchroniczne Stopped w grace'u (bez flagi), podniesienie flagi w fazie running, odrzucenie duplikatowego żądania, i sprawdzenia walidacji.
  • Stop grace'u-pauzowanego utrzymuje Stopped bezpośrednio bez flagi cache'u (synchroniczny, jedyny pisarz)
  • Stop fazy running-owej używa flagi cache'u (asynchroniczny pickup następnym krokiem); drugi stop podczas oczekującej flagi jest odrzucany
  • Stop na nieaktywnym (już terminalnym/zatrzymanym) ingestion'ie jest odrzucany z DomainValidationException
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/SucceedIngestionWhenGeneratedOrderCompletedEventHandlerTests.csśrednie
Finalizacja oparta na zdarzeniach: ingestion czekający na ukończenie wygenerowanego zlecenia, idempotentny no-op dla starych/niepowiązanych zdarzeń.
  • Late/duplikatowe zdarzenie gdy ingestion już Succeeded jest no-op (brak zapisu)
  • Zdarzenie bez pasującego IboGenerated ingestion'u jest cicho porzucane (brak zapisu)
  • Ukończone zlecenie przechodzi ingestion z IboGenerated do Succeeded i broadcastuje
src/WOCK.WholeSale.Tests/Deliveries/InboundDeliveryIngestion/UploadKeysStepHandlerTests.csśrednie
Upload do magazynu blob: upload klucza pliku z weryfikacją sumy kontrolnej, pominięcie tylko tekstu, i koordynacja mirror'u.
  • Niezgodność sumy kontrolnej rzuca wyjątek (nie awaria biznesowa) aby Hangfire ponowił upload
  • Ładunek tylko tekstu omija krok ale nadal przesuwa łańcuch (pwork pre-converge ukończony)
  • Asynchroniczne wezwanie mirror'u jest enqueue'owane dla każdego przesłanego pliku bez blokowania ukończenia kroku

Sprawdź też (poza listą plików)

Migracje EF

Na liście w etapie 17 — sprawdź, czy migracja Up() zgadza się z konfiguracjami i jest tylko-w-przód; pliki *.Designer / snapshot są generowane przez EF.

Rejestracje i DI

Etap 15 obejmuje Startup/Program. Potwierdź, że serwisy/fabryki, hostowany reloader konfiguracji, filtr Hangfire od trwałych błędów oraz rejestracje SignalR/cron są obecne.

Współdzielone agregaty

Etap 3 wyodrębnia zmiany w InboundDelivery/Key — przeglądaj tylko te fragmenty (bramka skip-key-upload, wcześniej policzone nazwy blobów), nie całe pliki.

Narzędzie testowe

Nie jest częścią PR-a API: tools/ingestion-tester to narzędzie deweloperskie (ta strona w nim żyje).