mirror of
https://github.com/hanzoai/notify.git
synced 2026-11-02 09:36:47 +00:00
* kmsbridge: drop legacy-Infisical workaround framing in doc comments
kmsbridge has been the canonical path to Hanzo KMS since the canonical
migration landed. Update doc comments to reflect that — the package is the
direct line to /v1/kms/orgs/{org}/secrets, not a stopgap around a base/plugins
version pin. The base.KMSClient surface compatibility note stays since callers
do still swap import paths to migrate.
* notify: SES SendRawEmail + List-Unsubscribe injection on marketing class
Per RFC 8058 + Gmail/Outlook bulk-sender policy (2024+), marketing email
must carry both List-Unsubscribe and List-Unsubscribe-Post to surface
the native one-click unsubscribe button. The SES SDK's SendEmail cannot
inject custom headers; SendRawEmail takes raw MIME instead.
Substrates:
- service/amazonses/raw.go: AmazonSES.SendRaw method, builds the MIME
envelope (From / To / Subject / Date / MIME-Version / Content-Type +
sorted extra headers + blank line + body) and ships via SendRawEmail.
Header smuggling (CR/LF in extra header values) is rejected at
compose time. SendEmail path is untouched.
- internal/marketing/category.go: IsMarketing predicate matching the
five §3.2 buckets (promotional, newsletter, product_update,
partner_offer, research) + HTML / plain footer injection helpers.
- internal/tasks/activities.go: marketing branch on Activities.send.
When (a) email channel, (b) marketing category, (c) marketing.Config
wired: mint unsubscribe token, persist row into
notification_unsubscribe_tokens BEFORE send, append visible footer
to the body, route through chain.RunMarketing.
- internal/tenant/chain.go: ChainProvider gains an optional RawSender
extension. RunMarketing prefers SendRaw when implemented (today: SES
API only), falls back to plain Send otherwise. Per-attempt + whole-
chain deadlines and the retry classifier are unchanged.
- pkg/types + internal/tasks/workflow: SendRequest / SendInput gain
Category, UserID, IsHTML fields.
- cmd/notifyd/main.go: wires marketing.Config (NOTIFY_ENV + unsubSigner)
onto Activities at boot. NOTIFY_UNSUBSCRIBE_SECRET also gates this
path (same fail-closed rule that already gates /unsubscribe routes).
Tests (37 added across 4 files):
- service/amazonses/raw_test.go — MIME envelope shape; sorted header
ordering; HTML vs plain Content-Type; multi-recipient To header;
header-smuggling rejection; SDK error propagation.
- internal/marketing/category_test.go — IsMarketing buckets + edges;
HTML + plain footer injection; empty URL no-op.
- internal/tasks/marketing_test.go — token persistence;
three-gate shouldUseMarketing decision matrix; idempotent re-persist;
extractToken URL parsing.
- internal/tenant/chain_marketing_test.go — RunMarketing prefers
RawSender path; falls back to Send when RawSender absent; multi-
provider fallthrough on retryable failure.
Completes the PR #7 follow-up: the header builder shipped in #7 is now
wired into the send path; one-click unsubscribe works end-to-end for
marketing-class email.
186 lines
6.0 KiB
Go
186 lines
6.0 KiB
Go
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
|
|
|
package amazonses
|
|
|
|
import (
|
|
context "context"
|
|
|
|
ses "github.com/aws/aws-sdk-go-v2/service/ses"
|
|
mock "github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
// mocksesClient is an autogenerated mock type for the sesClient type
|
|
type mocksesClient struct {
|
|
mock.Mock
|
|
}
|
|
|
|
type mocksesClient_Expecter struct {
|
|
mock *mock.Mock
|
|
}
|
|
|
|
func (_m *mocksesClient) EXPECT() *mocksesClient_Expecter {
|
|
return &mocksesClient_Expecter{mock: &_m.Mock}
|
|
}
|
|
|
|
// SendEmail provides a mock function with given fields: ctx, params, optFns
|
|
func (_m *mocksesClient) SendEmail(ctx context.Context, params *ses.SendEmailInput, optFns ...func(*ses.Options)) (*ses.SendEmailOutput, error) {
|
|
_va := make([]interface{}, len(optFns))
|
|
for _i := range optFns {
|
|
_va[_i] = optFns[_i]
|
|
}
|
|
var _ca []interface{}
|
|
_ca = append(_ca, ctx, params)
|
|
_ca = append(_ca, _va...)
|
|
ret := _m.Called(_ca...)
|
|
|
|
if len(ret) == 0 {
|
|
panic("no return value specified for SendEmail")
|
|
}
|
|
|
|
var r0 *ses.SendEmailOutput
|
|
var r1 error
|
|
if rf, ok := ret.Get(0).(func(context.Context, *ses.SendEmailInput, ...func(*ses.Options)) (*ses.SendEmailOutput, error)); ok {
|
|
return rf(ctx, params, optFns...)
|
|
}
|
|
if rf, ok := ret.Get(0).(func(context.Context, *ses.SendEmailInput, ...func(*ses.Options)) *ses.SendEmailOutput); ok {
|
|
r0 = rf(ctx, params, optFns...)
|
|
} else {
|
|
if ret.Get(0) != nil {
|
|
r0 = ret.Get(0).(*ses.SendEmailOutput)
|
|
}
|
|
}
|
|
|
|
if rf, ok := ret.Get(1).(func(context.Context, *ses.SendEmailInput, ...func(*ses.Options)) error); ok {
|
|
r1 = rf(ctx, params, optFns...)
|
|
} else {
|
|
r1 = ret.Error(1)
|
|
}
|
|
|
|
return r0, r1
|
|
}
|
|
|
|
// mocksesClient_SendEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendEmail'
|
|
type mocksesClient_SendEmail_Call struct {
|
|
*mock.Call
|
|
}
|
|
|
|
// SendEmail is a helper method to define mock.On call
|
|
// - ctx context.Context
|
|
// - params *ses.SendEmailInput
|
|
// - optFns ...func(*ses.Options)
|
|
func (_e *mocksesClient_Expecter) SendEmail(ctx interface{}, params interface{}, optFns ...interface{}) *mocksesClient_SendEmail_Call {
|
|
return &mocksesClient_SendEmail_Call{Call: _e.mock.On("SendEmail",
|
|
append([]interface{}{ctx, params}, optFns...)...)}
|
|
}
|
|
|
|
func (_c *mocksesClient_SendEmail_Call) Run(run func(ctx context.Context, params *ses.SendEmailInput, optFns ...func(*ses.Options))) *mocksesClient_SendEmail_Call {
|
|
_c.Call.Run(func(args mock.Arguments) {
|
|
variadicArgs := make([]func(*ses.Options), len(args)-2)
|
|
for i, a := range args[2:] {
|
|
if a != nil {
|
|
variadicArgs[i] = a.(func(*ses.Options))
|
|
}
|
|
}
|
|
run(args[0].(context.Context), args[1].(*ses.SendEmailInput), variadicArgs...)
|
|
})
|
|
return _c
|
|
}
|
|
|
|
func (_c *mocksesClient_SendEmail_Call) Return(_a0 *ses.SendEmailOutput, _a1 error) *mocksesClient_SendEmail_Call {
|
|
_c.Call.Return(_a0, _a1)
|
|
return _c
|
|
}
|
|
|
|
func (_c *mocksesClient_SendEmail_Call) RunAndReturn(run func(context.Context, *ses.SendEmailInput, ...func(*ses.Options)) (*ses.SendEmailOutput, error)) *mocksesClient_SendEmail_Call {
|
|
_c.Call.Return(run)
|
|
return _c
|
|
}
|
|
|
|
// SendRawEmail provides a mock function with given fields: ctx, params, optFns
|
|
func (_m *mocksesClient) SendRawEmail(ctx context.Context, params *ses.SendRawEmailInput, optFns ...func(*ses.Options)) (*ses.SendRawEmailOutput, error) {
|
|
_va := make([]interface{}, len(optFns))
|
|
for _i := range optFns {
|
|
_va[_i] = optFns[_i]
|
|
}
|
|
var _ca []interface{}
|
|
_ca = append(_ca, ctx, params)
|
|
_ca = append(_ca, _va...)
|
|
ret := _m.Called(_ca...)
|
|
|
|
if len(ret) == 0 {
|
|
panic("no return value specified for SendRawEmail")
|
|
}
|
|
|
|
var r0 *ses.SendRawEmailOutput
|
|
var r1 error
|
|
if rf, ok := ret.Get(0).(func(context.Context, *ses.SendRawEmailInput, ...func(*ses.Options)) (*ses.SendRawEmailOutput, error)); ok {
|
|
return rf(ctx, params, optFns...)
|
|
}
|
|
if rf, ok := ret.Get(0).(func(context.Context, *ses.SendRawEmailInput, ...func(*ses.Options)) *ses.SendRawEmailOutput); ok {
|
|
r0 = rf(ctx, params, optFns...)
|
|
} else {
|
|
if ret.Get(0) != nil {
|
|
r0 = ret.Get(0).(*ses.SendRawEmailOutput)
|
|
}
|
|
}
|
|
|
|
if rf, ok := ret.Get(1).(func(context.Context, *ses.SendRawEmailInput, ...func(*ses.Options)) error); ok {
|
|
r1 = rf(ctx, params, optFns...)
|
|
} else {
|
|
r1 = ret.Error(1)
|
|
}
|
|
|
|
return r0, r1
|
|
}
|
|
|
|
// mocksesClient_SendRawEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendRawEmail'
|
|
type mocksesClient_SendRawEmail_Call struct {
|
|
*mock.Call
|
|
}
|
|
|
|
// SendRawEmail is a helper method to define mock.On call
|
|
// - ctx context.Context
|
|
// - params *ses.SendRawEmailInput
|
|
// - optFns ...func(*ses.Options)
|
|
func (_e *mocksesClient_Expecter) SendRawEmail(ctx interface{}, params interface{}, optFns ...interface{}) *mocksesClient_SendRawEmail_Call {
|
|
return &mocksesClient_SendRawEmail_Call{Call: _e.mock.On("SendRawEmail",
|
|
append([]interface{}{ctx, params}, optFns...)...)}
|
|
}
|
|
|
|
func (_c *mocksesClient_SendRawEmail_Call) Run(run func(ctx context.Context, params *ses.SendRawEmailInput, optFns ...func(*ses.Options))) *mocksesClient_SendRawEmail_Call {
|
|
_c.Call.Run(func(args mock.Arguments) {
|
|
variadicArgs := make([]func(*ses.Options), len(args)-2)
|
|
for i, a := range args[2:] {
|
|
if a != nil {
|
|
variadicArgs[i] = a.(func(*ses.Options))
|
|
}
|
|
}
|
|
run(args[0].(context.Context), args[1].(*ses.SendRawEmailInput), variadicArgs...)
|
|
})
|
|
return _c
|
|
}
|
|
|
|
func (_c *mocksesClient_SendRawEmail_Call) Return(_a0 *ses.SendRawEmailOutput, _a1 error) *mocksesClient_SendRawEmail_Call {
|
|
_c.Call.Return(_a0, _a1)
|
|
return _c
|
|
}
|
|
|
|
func (_c *mocksesClient_SendRawEmail_Call) RunAndReturn(run func(context.Context, *ses.SendRawEmailInput, ...func(*ses.Options)) (*ses.SendRawEmailOutput, error)) *mocksesClient_SendRawEmail_Call {
|
|
_c.Call.Return(run)
|
|
return _c
|
|
}
|
|
|
|
// newMocksesClient creates a new instance of mocksesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
// The first argument is typically a *testing.T value.
|
|
func newMocksesClient(t interface {
|
|
mock.TestingT
|
|
Cleanup(func())
|
|
}) *mocksesClient {
|
|
mock := &mocksesClient{}
|
|
mock.Mock.Test(t)
|
|
|
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
|
|
return mock
|
|
}
|