Files
Assistant/adapters/notificationexecutor/executor_test.go
m 6e0040fe89
CI / test (pull_request) Successful in 12s
CI / test (push) Successful in 10s
feat: add notification delivery foundation
2026-08-16 19:08:07 +03:00

110 lines
4.6 KiB
Go

package notificationexecutor
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"git.cin.su/m/Assistant/contracts/capability"
commitcontract "git.cin.su/m/Assistant/contracts/commit"
"git.cin.su/m/Assistant/contracts/identity"
)
func testAction(t *testing.T) commitcontract.AuthorizedAction {
t.Helper()
payload := Payload{ActionIntentID: "intent-1", TemplateCode: commitcontract.ProspectiveDeliveredTemplateV1, ProspectiveItemID: "item-1", ExpectedRevision: 2, Title: "Домовой", Message: "Пора вернуться к задаче"}
digest, err := Digest(payload)
if err != nil {
t.Fatal(err)
}
return commitcontract.AuthorizedAction{PrincipalID: identity.PrincipalID("owner-1"), Proposal: commitcontract.ActionProposal{
IntentID: payload.ActionIntentID, CapabilityID: commitcontract.CapabilityProspectiveNotification, Operation: commitcontract.OperationDeliverProspectiveNotification,
NotificationSubject: commitcontract.ProspectiveNotificationSubject{ProspectiveItemID: "item-1", ExpectedRevision: 2, TemplateCode: payload.TemplateCode, BindingID: "binding-1", BindingRevision: 1, RenderedTitle: payload.Title, RenderedMessage: payload.Message, RequestDigest: digest},
}}
}
func testBinding(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 TestExecutorPutAndReconcileMatchReceipt(t *testing.T) {
action := testAction(t)
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer secret" {
t.Error("missing credential")
}
if r.Method == http.MethodPut && r.URL.Path != "/delivery/v1/deliveries/intent-1" {
t.Errorf("bad put path %s", r.URL.Path)
}
if r.Method == http.MethodGet && r.URL.Path != "/delivery/v1/receipts/intent-1" {
t.Errorf("bad get path %s", r.URL.Path)
}
_ = json.NewEncoder(w).Encode(Receipt{ReceiptID: "receipt-intent-1", ActionIntentID: "intent-1", RequestDigest: action.Proposal.NotificationSubject.RequestDigest, AcceptedAt: 1})
}))
defer server.Close()
executor, err := New(testBinding(server.URL), "secret", server.Client())
if err != nil {
t.Fatal(err)
}
if result := executor.Execute(action); !result.Known || !result.Succeeded {
t.Fatalf("execute: %+v", result)
}
if result := executor.Reconcile(action); !result.Known || !result.Succeeded {
t.Fatalf("reconcile: %+v", result)
}
}
func TestExecutorAmbiguousAndIntegrityOutcomes(t *testing.T) {
action := testAction(t)
for name, handler := range map[string]http.HandlerFunc{
"server error": func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "no", http.StatusBadGateway) },
"bad receipt": func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(Receipt{ReceiptID: "r", ActionIntentID: "other", RequestDigest: action.Proposal.NotificationSubject.RequestDigest, AcceptedAt: 1})
},
} {
t.Run(name, func(t *testing.T) {
server := httptest.NewTLSServer(handler)
defer server.Close()
executor, err := New(testBinding(server.URL), "secret", server.Client())
if err != nil {
t.Fatal(err)
}
if result := executor.Execute(action); result.Known {
t.Fatalf("must be unknown: %+v", result)
}
})
}
action.Proposal.NotificationSubject.RequestDigest = "bad"
server := httptest.NewTLSServer(http.NotFoundHandler())
defer server.Close()
executor, err := New(testBinding(server.URL), "secret", server.Client())
if err != nil {
t.Fatal(err)
}
if result := executor.Execute(action); !result.Known || result.Succeeded {
t.Fatalf("invalid evidence must fail closed: %+v", result)
}
}
func TestExecutorDoesNotFollowRedirect(t *testing.T) {
action := testAction(t)
targetCalls := 0
target := httptest.NewTLSServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { targetCalls++ }))
defer target.Close()
redirect := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect)
}))
defer redirect.Close()
executor, err := New(testBinding(redirect.URL), "secret", redirect.Client())
if err != nil {
t.Fatal(err)
}
if result := executor.Execute(action); result.Known {
t.Fatalf("redirect must be ambiguous: %+v", result)
}
if targetCalls != 0 {
t.Fatal("redirect target was called")
}
}