126 lines
6.3 KiB
Go
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)
|
|
}
|
|
}
|