complete MECE request

This commit is contained in:
Stephan D
2026-02-24 18:33:12 +01:00
parent 4c5677202a
commit a998b59072
34 changed files with 957 additions and 156 deletions

View File

@@ -15,6 +15,7 @@ import (
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
"go.mongodb.org/mongo-driver/v2/bson"
)
@@ -167,6 +168,68 @@ func TestBuildPlan_RequiresFXUsesSettlementCurrencyForDestinationStep(t *testing
}
}
func TestBuildPlan_ResolvesIndependentEconomicsKnobs(t *testing.T) {
svc := New(nil)
orgID := bson.NewObjectID()
intent := sampleCardQuoteIntent()
intent.SettlementMode = transfer_intent_hydrator.QuoteSettlementModeFixReceived
intent.FeeTreatment = transfer_intent_hydrator.QuoteFeeTreatmentDeductFromDestination
planModel, err := svc.BuildPlan(context.Background(), ComputeInput{
OrganizationRef: orgID.Hex(),
OrganizationID: orgID,
BaseIdempotencyKey: "idem-key",
Intents: []*transfer_intent_hydrator.QuoteIntent{intent},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if planModel == nil || len(planModel.Items) != 1 {
t.Fatalf("expected single plan item")
}
item := planModel.Items[0]
if item == nil {
t.Fatalf("expected plan item")
}
if got, want := item.ResolvedSettlementMode, paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED; got != want {
t.Fatalf("unexpected resolved settlement mode: got=%s want=%s", got.String(), want.String())
}
if got, want := item.ResolvedFeeTreatment, quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION; got != want {
t.Fatalf("unexpected resolved fee treatment: got=%s want=%s", got.String(), want.String())
}
}
func TestBuildPlan_DefaultsResolvedFeeTreatmentWhenUnspecified(t *testing.T) {
svc := New(nil)
orgID := bson.NewObjectID()
intent := sampleCardQuoteIntent()
intent.SettlementMode = transfer_intent_hydrator.QuoteSettlementModeFixReceived
intent.FeeTreatment = transfer_intent_hydrator.QuoteFeeTreatmentUnspecified
planModel, err := svc.BuildPlan(context.Background(), ComputeInput{
OrganizationRef: orgID.Hex(),
OrganizationID: orgID,
BaseIdempotencyKey: "idem-key",
Intents: []*transfer_intent_hydrator.QuoteIntent{intent},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if planModel == nil || len(planModel.Items) != 1 {
t.Fatalf("expected single plan item")
}
item := planModel.Items[0]
if item == nil {
t.Fatalf("expected plan item")
}
if got, want := item.ResolvedSettlementMode, paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED; got != want {
t.Fatalf("unexpected resolved settlement mode: got=%s want=%s", got.String(), want.String())
}
if got, want := item.ResolvedFeeTreatment, quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE; got != want {
t.Fatalf("unexpected default resolved fee treatment: got=%s want=%s", got.String(), want.String())
}
}
func TestBuildPlan_SelectsGatewaysAndIgnoresIrrelevant(t *testing.T) {
svc := New(nil, WithGatewayRegistry(staticGatewayRegistry{
items: []*model.GatewayInstanceDescriptor{
@@ -405,6 +468,49 @@ func TestCompute_EnrichesRouteConditionsAndTotalCost(t *testing.T) {
if got := core.lastQuoteIn.ExecutionConditions.GetPrefundingRequired(); !got {
t.Fatalf("expected prefunding_required in build quote input for reserve mode")
}
if got, want := result.Quote.ResolvedSettlementMode, paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE; got != want {
t.Fatalf("unexpected resolved settlement mode: got=%s want=%s", got.String(), want.String())
}
if got, want := result.Quote.ResolvedFeeTreatment, quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE; got != want {
t.Fatalf("unexpected resolved fee treatment: got=%s want=%s", got.String(), want.String())
}
}
func TestCompute_PropagatesIndependentResolvedEconomics(t *testing.T) {
core := &fakeCore{
quote: &ComputedQuote{
DebitAmount: &moneyv1.Money{Amount: "100", Currency: "USD"},
},
expiresAt: time.Unix(1000, 0),
}
svc := New(core)
orgID := bson.NewObjectID()
intent := sampleCardQuoteIntent()
intent.SettlementMode = transfer_intent_hydrator.QuoteSettlementModeFixReceived
intent.FeeTreatment = transfer_intent_hydrator.QuoteFeeTreatmentDeductFromDestination
output, err := svc.Compute(context.Background(), ComputeInput{
OrganizationRef: orgID.Hex(),
OrganizationID: orgID,
PreviewOnly: true,
Intents: []*transfer_intent_hydrator.QuoteIntent{intent},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output == nil || len(output.Results) != 1 {
t.Fatalf("expected single result")
}
if output.Results[0].Quote == nil {
t.Fatalf("expected quote")
}
if got, want := output.Results[0].Quote.ResolvedSettlementMode, paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED; got != want {
t.Fatalf("unexpected resolved settlement mode: got=%s want=%s", got.String(), want.String())
}
if got, want := output.Results[0].Quote.ResolvedFeeTreatment, quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION; got != want {
t.Fatalf("unexpected resolved fee treatment: got=%s want=%s", got.String(), want.String())
}
}
func TestCompute_PreviewMarksIndicativeReadiness(t *testing.T) {

View File

@@ -0,0 +1,71 @@
package quote_computation_service
import (
"testing"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
)
func TestEnsureComputedQuote_UsesItemResolvedEconomicsWhenQuoteUnset(t *testing.T) {
src := &ComputedQuote{
DebitAmount: &moneyv1.Money{Amount: "10", Currency: "USD"},
}
item := &QuoteComputationPlanItem{
ResolvedSettlementMode: paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED,
ResolvedFeeTreatment: quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
}
got := ensureComputedQuote(src, item)
if got == nil {
t.Fatalf("expected quote")
}
if got.ResolvedSettlementMode != paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED {
t.Fatalf("unexpected resolved settlement mode: %s", got.ResolvedSettlementMode.String())
}
if got.ResolvedFeeTreatment != quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION {
t.Fatalf("unexpected resolved fee treatment: %s", got.ResolvedFeeTreatment.String())
}
}
func TestEnsureComputedQuote_PreservesQuoteResolvedEconomics(t *testing.T) {
src := &ComputedQuote{
DebitAmount: &moneyv1.Money{Amount: "10", Currency: "USD"},
ResolvedSettlementMode: paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE,
ResolvedFeeTreatment: quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
}
item := &QuoteComputationPlanItem{
ResolvedSettlementMode: paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED,
ResolvedFeeTreatment: quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
}
got := ensureComputedQuote(src, item)
if got == nil {
t.Fatalf("expected quote")
}
if got.ResolvedSettlementMode != paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE {
t.Fatalf("unexpected resolved settlement mode: %s", got.ResolvedSettlementMode.String())
}
if got.ResolvedFeeTreatment != quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE {
t.Fatalf("unexpected resolved fee treatment: %s", got.ResolvedFeeTreatment.String())
}
}
func TestEnsureComputedQuote_DefaultsResolvedEconomicsWhenUnset(t *testing.T) {
src := &ComputedQuote{
DebitAmount: &moneyv1.Money{Amount: "10", Currency: "USD"},
}
item := &QuoteComputationPlanItem{}
got := ensureComputedQuote(src, item)
if got == nil {
t.Fatalf("expected quote")
}
if got.ResolvedSettlementMode != paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE {
t.Fatalf("unexpected default settlement mode: %s", got.ResolvedSettlementMode.String())
}
if got.ResolvedFeeTreatment != quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE {
t.Fatalf("unexpected default fee treatment: %s", got.ResolvedFeeTreatment.String())
}
}

View File

@@ -78,6 +78,7 @@ func clonePaymentIntent(src model.PaymentIntent) model.PaymentIntent {
FX: nil,
FeePolicy: src.FeePolicy,
SettlementMode: src.SettlementMode,
FeeTreatment: src.FeeTreatment,
SettlementCurrency: strings.ToUpper(strings.TrimSpace(src.SettlementCurrency)),
Attributes: cloneStringMap(src.Attributes),
Customer: src.Customer,

View File

@@ -25,6 +25,7 @@ func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model
RequiresFX: src.RequiresFX,
Attributes: cloneStringMap(src.Attributes),
SettlementMode: modelSettlementMode(src.SettlementMode),
FeeTreatment: modelFeeTreatment(src.FeeTreatment),
SettlementCurrency: settlementCurrency,
}
}
@@ -106,3 +107,14 @@ func modelSettlementMode(mode transfer_intent_hydrator.QuoteSettlementMode) mode
return model.SettlementModeFixSource
}
}
func modelFeeTreatment(value transfer_intent_hydrator.QuoteFeeTreatment) model.FeeTreatment {
switch value {
case transfer_intent_hydrator.QuoteFeeTreatmentDeductFromDestination:
return model.FeeTreatmentDeductFromDestination
case transfer_intent_hydrator.QuoteFeeTreatmentAddToSource:
return model.FeeTreatmentAddToSource
default:
return model.FeeTreatmentAddToSource
}
}