Files
coder/coderd/notifications/notificationstest/fake_enqueuer.go
Vincent Vielle 522181fead feat(coderd): add new dispatch logic for coder inbox (#16764)
This PR is [resolving the dispatch part of Coder
Inbocx](https://github.com/coder/internal/issues/403).

Since the DB layer has been merged - we now want to insert notifications
into Coder Inbox in parallel of the other delivery target.

To do so, we push two messages instead of one using the `Enqueue`
method.
2025-03-05 22:43:18 +01:00

123 lines
3.3 KiB
Go

package notificationstest
import (
"context"
"fmt"
"sync"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
)
type FakeEnqueuer struct {
authorizer rbac.Authorizer
mu sync.Mutex
sent []*FakeNotification
}
type FakeNotification struct {
UserID, TemplateID uuid.UUID
Labels map[string]string
Data map[string]any
CreatedBy string
Targets []uuid.UUID
}
// TODO: replace this with actual calls to dbauthz.
// See: https://github.com/coder/coder/issues/15481
func (f *FakeEnqueuer) assertRBACNoLock(ctx context.Context) {
if f.mu.TryLock() {
panic("Developer error: do not call assertRBACNoLock outside of a mutex lock!")
}
// If we get here, we are locked.
if f.authorizer == nil {
f.authorizer = rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
}
act, ok := dbauthz.ActorFromContext(ctx)
if !ok {
panic("Developer error: no actor in context, you may need to use dbauthz.AsNotifier(ctx)")
}
for _, a := range []policy.Action{policy.ActionCreate, policy.ActionRead} {
err := f.authorizer.Authorize(ctx, act, a, rbac.ResourceNotificationMessage)
if err == nil {
return
}
if rbac.IsUnauthorizedError(err) {
panic(fmt.Sprintf("Developer error: not authorized to %s %s. "+
"Ensure that you are using dbauthz.AsXXX with an actor that has "+
"policy.ActionCreate on rbac.ResourceNotificationMessage", a, rbac.ResourceNotificationMessage.Type))
}
panic("Developer error: failed to check auth:" + err.Error())
}
}
func (f *FakeEnqueuer) Enqueue(ctx context.Context, userID, templateID uuid.UUID, labels map[string]string, createdBy string, targets ...uuid.UUID) ([]uuid.UUID, error) {
return f.EnqueueWithData(ctx, userID, templateID, labels, nil, createdBy, targets...)
}
func (f *FakeEnqueuer) EnqueueWithData(ctx context.Context, userID, templateID uuid.UUID, labels map[string]string, data map[string]any, createdBy string, targets ...uuid.UUID) ([]uuid.UUID, error) {
return f.enqueueWithDataLock(ctx, userID, templateID, labels, data, createdBy, targets...)
}
func (f *FakeEnqueuer) enqueueWithDataLock(ctx context.Context, userID, templateID uuid.UUID, labels map[string]string, data map[string]any, createdBy string, targets ...uuid.UUID) ([]uuid.UUID, error) {
f.mu.Lock()
defer f.mu.Unlock()
f.assertRBACNoLock(ctx)
f.sent = append(f.sent, &FakeNotification{
UserID: userID,
TemplateID: templateID,
Labels: labels,
Data: data,
CreatedBy: createdBy,
Targets: targets,
})
id := uuid.New()
return []uuid.UUID{id}, nil
}
func (f *FakeEnqueuer) Clear() {
f.mu.Lock()
defer f.mu.Unlock()
f.sent = nil
}
func (f *FakeEnqueuer) Sent(matchers ...func(*FakeNotification) bool) []*FakeNotification {
f.mu.Lock()
defer f.mu.Unlock()
sent := []*FakeNotification{}
for _, notif := range f.sent {
// Check this notification matches all given matchers
matches := true
for _, matcher := range matchers {
if !matcher(notif) {
matches = false
break
}
}
if matches {
sent = append(sent, notif)
}
}
return sent
}
func WithTemplateID(id uuid.UUID) func(*FakeNotification) bool {
return func(n *FakeNotification) bool {
return n.TemplateID == id
}
}