package lkgateway import ( "context" "path/filepath" "sync/atomic" "testing" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" ) type flakySender struct{ fail bool } func (f *flakySender) Send(_ context.Context, _ *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) { if f.fail { return nil, &igw.NotDeliveredError{PackageID: "187", Code: "ERRORXML_UPLOAD_FAILED"} } return nil, nil } func (f *flakySender) SendDecision(_ context.Context, _ *m2m.M2MTransferDecision) error { return nil } // НРД не принял пакет: заявка остаётся «Не отправлена», BJ_SENT не пишется; повтор — тот же GUID. func TestSendFailureAndResend(t *testing.T) { old := sendFailFile sendFailFile = filepath.Join(t.TempDir(), "sf.json") sendFails.loaded = false defer func() { sendFailFile, sendFails.loaded = old, false }() oldHook, oldQ := sentHook, quikSendHook defer func() { sentHook, quikSendHook = oldHook, oldQ }() quikSendHook = nil var sent atomic.Int32 sentHook = func(*m2m.M2MTransferRequest) { sent.Add(1) } ctx := context.Background() snd := &flakySender{fail: true} svc := NewService(Config{Repository: m2mcore.NewMemoryRepository(), Sender: snd}) req := sendFailRequest() if _, err := svc.SendBuiltRequest(ctx, req, "inv", nil); err == nil { t.Fatal("ожидалась ошибка доставки") } d, err := svc.Repo().GetByGUID(ctx, req.Header.GUID) if err != nil || d.State != m2mcore.StateValidated || d.Request == nil { t.Fatalf("заявка должна остаться «Не отправлена» с запросом: %+v %v", d, err) } if f := sendFailGet(string(req.Header.GUID)); f == nil || f.PackageID != "187" || sendFailText(f) == "" { t.Fatalf("неудача не записана: %+v", f) } if err := svc.ResendClaim(ctx, d.ID); err == nil { t.Fatal("НРД всё ещё недоступен — повтор тоже должен вернуть ошибку") } if f := sendFailGet(string(req.Header.GUID)); f == nil || f.Attempts != 2 { t.Fatalf("вторая попытка не учтена: %+v", f) } snd.fail = false if err := svc.ResendClaim(ctx, d.ID); err != nil { t.Fatal(err) } d2, _ := svc.Repo().GetByID(ctx, d.ID) if d2.ID != d.ID || d2.State != m2mcore.StateAwaitingDecision || sendFailGet(string(req.Header.GUID)) != nil { t.Fatalf("после повтора: %s %s", d2.ID, d2.State) } for i := 0; i < 100 && sent.Load() == 0; i++ { waitShort() } if sent.Load() != 1 { t.Fatalf("BJ_SENT должен уйти один раз — после доставки, было %d", sent.Load()) } } func sendFailRequest() *m2m.M2MTransferRequest { whole := uint64(100) isin := m2m.ISIN("RU0007661625") return &m2m.M2MTransferRequest{ Header: m2m.RequestHeader{ GUID: m2m.UUID("c02a1d5e-c2af-4799-bab4-953f133c5133"), SenderCode: "MC0079200000", ReceiverCode: "MC0010300000", CostInfo: m2m.CostInfo{No: &m2m.CostInfoNo{}}, }, Data: m2m.RequestData{ InvestorInformation: m2m.InvestorInformation{ LastName: "Иванов", FirstName: "Иван", IdentityDocument: m2m.IdentityDocument{ DocumentType: m2m.DocCode21, DocumentNumber: m2m.IdentityDocSerial("654321"), }, }, TransferringDepository: m2m.SettlementRequisites{INN: "7702070139"}, ReceivingDepository: m2m.SettlementRequisites{INN: "7802031669"}, TransferredSecurities: m2m.RequestTransferredSecurities{ Securities: []m2m.RequestSecurity{ { ReferenceID: "M2M2026030200001", SecurityCode: "MM0766162534", SecurityDetails: m2m.SecurityDetails{ISIN: &isin}, Quantity: m2m.Quantity{Whole: &whole}, SettlementAccount: []m2m.RequestSettlementAccount{ { SettlementRequisites: m2m.SettlementRequisites{INN: "7702070139"}, SettlementLocation: m2m.SettlementDepositoryLocation{ DeponentCode: "DP789456", AccountID: "31MC0021900000F01", SectionID: "P001", }, }, }, IsolationStatus: m2m.IsolationSGDN, }, }, }, }, } } func waitShort() { time.Sleep(10 * time.Millisecond) }