Files
Assistant/cmd/continuity-runtime/due_worker_test.go
m 18fbc7f105
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 15s
DEV-000065 fix notification binding origin
2026-08-17 16:33:05 +03:00

126 lines
6.3 KiB
Go

package main
import (
"context"
"testing"
"time"
"git.cin.su/m/Assistant/contracts/authority"
commitcontract "git.cin.su/m/Assistant/contracts/commit"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/platform/ids"
)
type memoryDueRepo struct {
item continuity.ProspectiveItem
attention continuity.AttentionItem
}
func (r *memoryDueRepo) DueArmed(context.Context, int64, int) ([]continuity.ProspectiveItem, error) {
if r.item.State == continuity.ProspectiveArmed {
return []continuity.ProspectiveItem{r.item}, nil
}
return nil, nil
}
func (r *memoryDueRepo) ClaimDueReminder(_ context.Context, id continuity.ProspectiveItemID, revision continuity.ProspectiveRevision, attentionID continuity.AttentionItemID, now int64) (continuity.ProspectiveItem, continuity.AttentionItem, error) {
if r.item.ID != id || r.item.Revision != revision || r.item.State != continuity.ProspectiveArmed {
return continuity.ProspectiveItem{}, continuity.AttentionItem{}, nil
}
r.item.State = continuity.ProspectiveDelivered
r.item.Revision += 3
r.attention = continuity.AttentionItem{ID: attentionID, OwnerPrincipalID: r.item.OwnerPrincipalID, ScopeID: r.item.ScopeID, SourceKind: continuity.AttentionSourceProspectiveReminder, SourceProspectiveID: r.item.ID, Level: continuity.AttentionDue, State: continuity.AttentionPending, Summary: r.item.Summary, DueAtUnixMillis: r.item.TriggerAtUnixMillis, CreatedAtUnixMillis: now, Revision: 1}
return r.item, r.attention, nil
}
func (r *memoryDueRepo) PendingAttention(context.Context, int) ([]continuity.AttentionItem, error) {
if r.attention.State == continuity.AttentionPending {
return []continuity.AttentionItem{r.attention}, nil
}
return nil, nil
}
func (r *memoryDueRepo) MarkAttentionDelivering(_ context.Context, id continuity.AttentionItemID, revision continuity.AttentionRevision) (continuity.AttentionItem, error) {
if r.attention.ID == id && r.attention.Revision == revision {
r.attention.State = continuity.AttentionDelivering
r.attention.Revision++
}
return r.attention, nil
}
func (r *memoryDueRepo) MarkAttentionDelivered(_ context.Context, id continuity.AttentionItemID, revision continuity.AttentionRevision, intentID continuity.CommittedActionIntentID, authorizedID continuity.CommittedAuthorizedActionID, attemptID continuity.CommittedActionAttemptID, outcome string, deliveredAt int64) (continuity.AttentionItem, error) {
if r.attention.ID == id && r.attention.Revision == revision {
r.attention.State = continuity.AttentionDelivered
r.attention.Revision++
r.attention.ActionIntentID = intentID
r.attention.AuthorizedActionID = authorizedID
r.attention.ActionAttemptID = attemptID
r.attention.DeliveryOutcome = outcome
r.attention.DeliveredAtUnixMillis = deliveredAt
}
return r.attention, nil
}
func (r *memoryDueRepo) MarkAttentionFailed(_ context.Context, id continuity.AttentionItemID, revision continuity.AttentionRevision, intentID continuity.CommittedActionIntentID, authorizedID continuity.CommittedAuthorizedActionID, attemptID continuity.CommittedActionAttemptID, outcome string) (continuity.AttentionItem, error) {
if r.attention.ID == id && r.attention.Revision == revision {
r.attention.State = continuity.AttentionFailed
r.attention.Revision++
r.attention.ActionIntentID = intentID
r.attention.AuthorizedActionID = authorizedID
r.attention.ActionAttemptID = attemptID
r.attention.DeliveryOutcome = outcome
}
return r.attention, nil
}
func (r *memoryDueRepo) Load(context.Context, continuity.ProspectiveItemID) (continuity.ProspectiveItem, error) {
return r.item, nil
}
type fakeDueNotifier struct{ calls int }
func (n *fakeDueNotifier) NotifyDue(context.Context, continuity.ProspectiveItem) (dueNotificationResponse, error) {
n.calls++
return dueNotificationResponse{ActionIntentID: "intent", AuthorizedActionID: "authorized", ActionAttemptID: "attempt", State: commitcontract.ActionSucceeded}, nil
}
type unknownDueNotifier struct{ calls int }
func (n *unknownDueNotifier) NotifyDue(context.Context, continuity.ProspectiveItem) (dueNotificationResponse, error) {
n.calls++
return dueNotificationResponse{ActionIntentID: "intent", AuthorizedActionID: "authorized", ActionAttemptID: "attempt", State: commitcontract.ActionUnknownOutcome}, nil
}
func TestDueReminderProducesAttentionAndNotificationOnce(t *testing.T) {
repo := &memoryDueRepo{item: continuity.ProspectiveItem{ID: "prospective", OwnerPrincipalID: identity.PrincipalID("owner"), ScopeID: authority.ScopeID("scope"), Summary: "проверить уведомление", TriggerAtUnixMillis: 10, State: continuity.ProspectiveArmed, Revision: 1}}
notifier := &fakeDueNotifier{}
now := time.UnixMilli(20)
worker := dueReminderWorker{repository: repo, commit: notifier, now: func() time.Time { return now }, newID: func() (ids.ID, error) { return ids.ID("attention"), nil }, limit: 10}
worker.process(context.Background())
worker.process(context.Background())
if repo.item.State != continuity.ProspectiveDelivered || repo.attention.State != continuity.AttentionDelivered {
t.Fatalf("unexpected states: prospective=%s attention=%s", repo.item.State, repo.attention.State)
}
if notifier.calls != 1 {
t.Fatalf("expected one notification call, got %d", notifier.calls)
}
if repo.attention.ActionIntentID == "" || repo.attention.DeliveredAtUnixMillis == 0 {
t.Fatal("delivery evidence was not persisted")
}
}
func TestDueReminderUnknownOutcomeDoesNotMarkAttentionDelivered(t *testing.T) {
repo := &memoryDueRepo{item: continuity.ProspectiveItem{ID: "prospective", OwnerPrincipalID: identity.PrincipalID("owner"), ScopeID: authority.ScopeID("scope"), Summary: "проверить уведомление", TriggerAtUnixMillis: 10, State: continuity.ProspectiveArmed, Revision: 1}}
notifier := &unknownDueNotifier{}
now := time.UnixMilli(20)
worker := dueReminderWorker{repository: repo, commit: notifier, now: func() time.Time { return now }, newID: func() (ids.ID, error) { return ids.ID("attention"), nil }, limit: 10}
worker.process(context.Background())
if repo.attention.State != continuity.AttentionFailed {
t.Fatalf("unknown outcome must not be delivered, got %s", repo.attention.State)
}
if repo.attention.DeliveryOutcome != string(commitcontract.ActionUnknownOutcome) {
t.Fatalf("missing unknown outcome evidence: %#v", repo.attention)
}
}