mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 00:01:14 +00:00
141 lines
5.2 KiB
Go
141 lines
5.2 KiB
Go
package models
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEventAllowedByScopes(t *testing.T) {
|
|
// An app with only READ_CONTACTS receives contact.* but not campaign.*.
|
|
scopes := APIPermReadContacts
|
|
if !EventAllowedByScopes(WebhookEventContactCreated, scopes) {
|
|
t.Error("contact.created should be allowed with READ_CONTACTS")
|
|
}
|
|
if EventAllowedByScopes(WebhookEventCampaignReplyReceived, scopes) {
|
|
t.Error("campaign.* must NOT be allowed without READ_CAMPAIGNS")
|
|
}
|
|
if EventAllowedByScopes(WebhookEventCRMDealCreated, scopes) {
|
|
t.Error("crm.* must NOT be allowed without READ_CRM")
|
|
}
|
|
// With both scopes, both pass.
|
|
both := APIPermReadContacts | APIPermReadCampaigns
|
|
if !EventAllowedByScopes(WebhookEventCampaignReplyReceived, both) {
|
|
t.Error("campaign.* should be allowed with READ_CAMPAIGNS")
|
|
}
|
|
}
|
|
|
|
func TestAppSubscribedEventTypes(t *testing.T) {
|
|
// Explicit wanted list is filtered by scope: campaign event dropped without
|
|
// READ_CAMPAIGNS, contact event kept.
|
|
wanted := []string{string(WebhookEventContactCreated), string(WebhookEventCampaignReplyReceived)}
|
|
got := AppSubscribedEventTypes(wanted, APIPermReadContacts)
|
|
if len(got) != 1 || got[0] != string(WebhookEventContactCreated) {
|
|
t.Errorf("expected only contact.created, got %v", got)
|
|
}
|
|
// Empty wanted = all non-firehose scope-allowed events; with only READ_CONTACTS
|
|
// it should include contact.* but never campaign.* or any firehose event.
|
|
all := AppSubscribedEventTypes(nil, APIPermReadContacts)
|
|
hasContact, hasCampaign, hasFirehose := false, false, false
|
|
for _, e := range all {
|
|
switch {
|
|
case e == string(WebhookEventContactCreated):
|
|
hasContact = true
|
|
case e == string(WebhookEventCampaignReplyReceived):
|
|
hasCampaign = true
|
|
case IsFirehoseEvent(WebhookEventType(e)):
|
|
hasFirehose = true
|
|
}
|
|
}
|
|
if !hasContact {
|
|
t.Error("empty filter with READ_CONTACTS should include contact.created")
|
|
}
|
|
if hasCampaign {
|
|
t.Error("empty filter with READ_CONTACTS must not include campaign.*")
|
|
}
|
|
if hasFirehose {
|
|
t.Error("empty filter must never include firehose events")
|
|
}
|
|
}
|
|
|
|
func verifiedEndpoint(eventTypes []string) *WebhookEndpoint {
|
|
now := time.Now()
|
|
return &WebhookEndpoint{Enabled: true, VerifiedAt: &now, EventTypes: eventTypes}
|
|
}
|
|
|
|
func TestSubscribes_RequiresEnabledAndVerified(t *testing.T) {
|
|
now := time.Now()
|
|
// Unverified endpoint never receives events, even with an explicit subscription.
|
|
unverified := &WebhookEndpoint{Enabled: true, EventTypes: []string{string(WebhookEventCampaignReplyReceived)}}
|
|
if unverified.Subscribes(WebhookEventCampaignReplyReceived) {
|
|
t.Error("an unverified endpoint must not receive events")
|
|
}
|
|
// Disabled endpoint never receives events.
|
|
disabled := &WebhookEndpoint{Enabled: false, VerifiedAt: &now}
|
|
if disabled.Subscribes(WebhookEventCampaignReplyReceived) {
|
|
t.Error("a disabled endpoint must not receive events")
|
|
}
|
|
}
|
|
|
|
func TestSubscribes_FirehoseOptIn(t *testing.T) {
|
|
all := verifiedEndpoint(nil) // empty filter = all NON-firehose
|
|
if !all.Subscribes(WebhookEventCampaignReplyReceived) {
|
|
t.Error("empty filter should receive normal events")
|
|
}
|
|
if all.Subscribes(WebhookEventCampaignEmailOpened) {
|
|
t.Error("empty filter must NOT receive firehose events")
|
|
}
|
|
|
|
optedIn := verifiedEndpoint([]string{string(WebhookEventCampaignEmailOpened)})
|
|
if !optedIn.Subscribes(WebhookEventCampaignEmailOpened) {
|
|
t.Error("explicit subscription should receive a firehose event")
|
|
}
|
|
if optedIn.Subscribes(WebhookEventCampaignReplyReceived) {
|
|
t.Error("an explicit filter should not match unlisted events")
|
|
}
|
|
}
|
|
|
|
func TestWebhookEventForAudit(t *testing.T) {
|
|
cases := []struct {
|
|
entity AuditEntityType
|
|
action AuditAction
|
|
want WebhookEventType
|
|
ok bool
|
|
}{
|
|
{AuditEntityCampaign, AuditActionCreate, WebhookEventCampaignCreated, true},
|
|
{AuditEntityCampaign, AuditActionStart, WebhookEventCampaignStarted, true},
|
|
{AuditEntityContact, AuditActionDelete, WebhookEventContactDeleted, true},
|
|
{AuditEntityCRMDeal, AuditActionUpdate, WebhookEventCRMDealUpdated, true},
|
|
{AuditEntitySettings, AuditActionUpdate, WebhookEventSettingsUpdated, true},
|
|
// Operator-internal + self-referential entities must NOT bridge.
|
|
{AuditEntityWorker, AuditActionCreate, "", false},
|
|
{AuditEntityWebhook, AuditActionCreate, "", false},
|
|
{AuditEntityAPIKey, AuditActionRotate, "", false},
|
|
{AuditEntityEmailAccount, AuditActionConnect, "", false}, // has a dedicated emit
|
|
}
|
|
for _, c := range cases {
|
|
got, ok := WebhookEventForAudit(c.entity, c.action)
|
|
if ok != c.ok || got != c.want {
|
|
t.Errorf("WebhookEventForAudit(%s,%s) = (%q,%v), want (%q,%v)", c.entity, c.action, got, ok, c.want, c.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWebhookEventCatalog_CoversFirehoseFlag(t *testing.T) {
|
|
if len(WebhookEventCatalog) == 0 {
|
|
t.Fatal("catalog must not be empty")
|
|
}
|
|
byType := map[WebhookEventType]WebhookEventDescriptor{}
|
|
for _, d := range WebhookEventCatalog {
|
|
byType[d.Type] = d
|
|
if !IsValidWebhookEventType(string(d.Type)) {
|
|
t.Errorf("catalog event %q is not a valid event type", d.Type)
|
|
}
|
|
}
|
|
if !byType[WebhookEventCampaignEmailOpened].Firehose {
|
|
t.Error("campaign.email_opened should be flagged firehose in the catalog")
|
|
}
|
|
if byType[WebhookEventCampaignReplyReceived].Firehose {
|
|
t.Error("campaign.reply_received should NOT be firehose")
|
|
}
|
|
}
|