9e6e95f431
- internal/m2mcore/fsm.go: конечный автомат с переходами Draft → Validated → SubmittedToNSD → AwaitingDecision → Confirmed → AwaitingSUB16 → Done, ветки Rejected/TimedOut/ManualApproval - internal/m2mcore/deal.go: доменная модель Deal с методами Validate/Submit/ReceiveDecision/Timeout/SendToManualApproval/ApproveManually/RejectManually/CompleteSUB16, журнал событий - internal/m2mcore/uuid.go: генератор UUID v4 без внешних зависимостей (crypto/rand) - internal/m2mcore/repo.go: порт Repository + MemoryRepository с идемпотентным Create по GUID - internal/m2mcore/ports.go: порты NSDSender/LKCallbackClient/CryptoVerifier/FansyStore с no-op заглушками для M1 - internal/m2mcore/enrich.go: EnrichRequest — сборка M2MTransferRequest из ClaimInput + Fansy, генерация ReferenceID по каждой ЦБ - internal/m2mcore/metrics.go: порт Recorder + MemoryRecorder в Prometheus-text формате - cmd/m2m-core/main.go: HTTP-сервер с /healthz и /metrics, graceful shutdown - migrations/m2m-core/001__deals.sql: схема для PostgreSQL-Repository (для M2) Покрытие: 63.1%. make ci зелёный. Без внешних Go-зависимостей (pgx и prometheus подключим в M2, когда прокси zetit откроет Go-модули). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
package m2mcore_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
|
|
)
|
|
|
|
func TestRepoCreateIdempotentByGUID(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := m2mcore.NewMemoryRepository()
|
|
|
|
// Используем валидный 36-символьный UUID (XSD НРД не требует битов
|
|
// версии/варианта, только длину и hex).
|
|
const guidStr = "c02a1d5e-c2af-4799-bab4-953f133c5133"
|
|
d1, err := m2mcore.NewDeal(m2m.UUID(guidStr), "inv-1", []byte("a"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d2, err := m2mcore.NewDeal(m2m.UUID(guidStr), "inv-1", []byte("a"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
saved1, err := repo.Create(ctx, d1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
saved2, err := repo.Create(ctx, d2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if saved1.ID != saved2.ID {
|
|
t.Errorf("идемпотентность нарушена: %s != %s", saved1.ID, saved2.ID)
|
|
}
|
|
}
|
|
|
|
func TestRepoGetByGUIDNotFound(t *testing.T) {
|
|
repo := m2mcore.NewMemoryRepository()
|
|
_, err := repo.GetByGUID(context.Background(), m2m.UUID("c02a1d5e-c2af-4799-bab4-953f133c5133"))
|
|
if !errors.Is(err, m2mcore.ErrNotFound) {
|
|
t.Errorf("ожидалась ErrNotFound, получено %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRepoListFilters(t *testing.T) {
|
|
ctx := context.Background()
|
|
repo := m2mcore.NewMemoryRepository()
|
|
|
|
mkDeal := func(guid, inv string) {
|
|
t.Helper()
|
|
d, err := m2mcore.NewDeal(m2m.UUID(guid), inv, []byte("x"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.Create(ctx, d); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
mkDeal("00000000-0000-0000-0000-000000000001", "inv-A")
|
|
mkDeal("00000000-0000-0000-0000-000000000002", "inv-A")
|
|
mkDeal("00000000-0000-0000-0000-000000000003", "inv-B")
|
|
|
|
all, err := repo.List(ctx, m2mcore.Filter{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(all) != 3 {
|
|
t.Errorf("ожидалось 3 сделки, получено %d", len(all))
|
|
}
|
|
|
|
onlyA, err := repo.List(ctx, m2mcore.Filter{InvestorID: "inv-A"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(onlyA) != 2 {
|
|
t.Errorf("ожидалось 2 сделки inv-A, получено %d", len(onlyA))
|
|
}
|
|
}
|