package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "git.cin.su/m/Assistant/adapters/notificationexecutor" "git.cin.su/m/Assistant/contracts/capability" commitcontract "git.cin.su/m/Assistant/contracts/commit" "git.cin.su/m/Assistant/contracts/identity" ) type fakeNotificationBindings struct { exact capability.Binding active capability.Binding exactErr error activeErr error } func (f fakeNotificationBindings) CapabilityBinding(context.Context, capability.CapabilityBindingID, int64) (capability.Binding, error) { return f.exact, f.exactErr } func (f fakeNotificationBindings) ActiveCapabilityBinding(context.Context, capability.CapabilityID, identity.PrincipalID) (capability.Binding, error) { return f.active, f.activeErr } func dispatcherAction(t *testing.T, binding capability.Binding) commitcontract.AuthorizedAction { t.Helper() payload := notificationexecutor.Payload{ActionIntentID: "intent-1", TemplateCode: commitcontract.ProspectiveDeliveredTemplateV1, ProspectiveItemID: "item-1", ExpectedRevision: 1, Title: "Домовой", Message: "Пора вернуться"} digest, err := notificationexecutor.Digest(payload) if err != nil { t.Fatal(err) } return commitcontract.AuthorizedAction{PrincipalID: binding.OwnerPrincipalID, Proposal: commitcontract.ActionProposal{IntentID: payload.ActionIntentID, CapabilityID: commitcontract.CapabilityProspectiveNotification, Operation: commitcontract.OperationDeliverProspectiveNotification, NotificationSubject: commitcontract.ProspectiveNotificationSubject{ProspectiveItemID: "item-1", ExpectedRevision: 1, TemplateCode: payload.TemplateCode, BindingID: binding.ID, BindingRevision: binding.Revision, RenderedTitle: payload.Title, RenderedMessage: payload.Message, RequestDigest: digest}}} } func dispatcherBinding(origin string) capability.Binding { return capability.Binding{ID: "binding-1", CapabilityID: commitcontract.CapabilityProspectiveNotification, OwnerPrincipalID: "owner-1", Revision: 1, State: capability.BindingActive, HTTPSOrigin: origin, DeliveryPath: "/delivery/v1/deliveries", ReceiptPath: "/delivery/v1/receipts", Protocol: capability.ProtocolIdempotentReceiptV1, SecretRef: "write-v1", VerifiedAtUnixMillis: 1, Health: capability.HealthAvailable} } func TestOperationDispatcherRequiresExactCurrentBinding(t *testing.T) { var action commitcontract.AuthorizedAction server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(notificationexecutor.Receipt{ReceiptID: "receipt-1", ActionIntentID: action.Proposal.IntentID, RequestDigest: action.Proposal.NotificationSubject.RequestDigest, AcceptedAt: 1}) })) defer server.Close() binding := dispatcherBinding(server.URL) action = dispatcherAction(t, binding) fallback := &knownExecutor{} dispatcher := operationDispatcher{continuityExecutor: fallback, continuityReconciler: fallback, bindings: fakeNotificationBindings{exact: binding, active: binding}, secretRef: "write-v1", secret: "secret", client: server.Client()} if result := dispatcher.Execute(action); !result.Known || !result.Succeeded { t.Fatalf("execute=%+v", result) } stale := binding stale.Revision = 2 dispatcher.bindings = fakeNotificationBindings{exact: binding, active: stale} if result := dispatcher.Execute(action); !result.Known || result.Succeeded { t.Fatalf("stale binding=%+v", result) } } func TestOperationDispatcherReconcilesRetiredHistoricalBinding(t *testing.T) { var action commitcontract.AuthorizedAction server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { t.Fatal("reconciliation mutated receiver") } _ = json.NewEncoder(w).Encode(notificationexecutor.Receipt{ReceiptID: "receipt-1", ActionIntentID: action.Proposal.IntentID, RequestDigest: action.Proposal.NotificationSubject.RequestDigest, AcceptedAt: 1}) })) defer server.Close() binding := dispatcherBinding(server.URL) action = dispatcherAction(t, binding) binding.State = capability.BindingRetired binding.Revision = 1 fallback := &knownExecutor{} dispatcher := operationDispatcher{continuityExecutor: fallback, continuityReconciler: fallback, bindings: fakeNotificationBindings{exact: binding}, secretRef: "write-v1", secret: "secret", client: server.Client()} if result := dispatcher.Reconcile(action); !result.Known || !result.Succeeded { t.Fatalf("reconcile=%+v", result) } }