diff --git a/app/models/user.go b/app/models/user.go index 6985bdc..ebef2bb 100644 --- a/app/models/user.go +++ b/app/models/user.go @@ -3,7 +3,9 @@ package models import ( "database/sql/driver" "errors" + "strconv" + "github.com/goravel/framework/contracts/notification" "github.com/goravel/framework/database/orm" "github.com/goravel/framework/support/json" ) @@ -40,14 +42,21 @@ func (r *UserTag) Value() (driver.Value, error) { return json.Marshal(r) } -// RouteNotificationFor resolves the delivery address per channel. +// RouteNotificationForMail implements contracts/notification.MailRoutable: +// the type-safe mail delivery route, preferred over RouteNotificationFor. +func (r *User) RouteNotificationForMail(notification notification.Notification) map[string]string { + return map[string]string{r.Mail: r.Name} +} + +// RouteNotificationForDatabase implements contracts/notification.DatabaseRoutable: +// the type-safe database delivery route, preferred over RouteNotificationFor. +func (r *User) RouteNotificationForDatabase() string { + return strconv.FormatUint(uint64(r.ID), 10) +} + +// RouteNotificationFor resolves the delivery address per channel. The built-in +// mail and database channels resolve via the typed interfaces above, so no +// route is left to match on the channel name here. func (r *User) RouteNotificationFor(channel string) any { - switch channel { - case "mail": - return r.Mail - case "database": - return r.ID - default: - return nil - } + return nil } diff --git a/app/notifications/order_processed.go b/app/notifications/order_processed.go index 1b85765..d087874 100644 --- a/app/notifications/order_processed.go +++ b/app/notifications/order_processed.go @@ -13,7 +13,7 @@ func NewOrderProcessed(orderID string) *OrderProcessed { } func (r *OrderProcessed) Via(notifiable notification.Notifiable) []string { - return []string{"database"} + return []string{notification.ChannelDatabase} } func (r *OrderProcessed) ToDatabase(notifiable notification.Notifiable) map[string]any { diff --git a/app/notifications/order_shipped.go b/app/notifications/order_shipped.go index c090999..09c14bd 100644 --- a/app/notifications/order_shipped.go +++ b/app/notifications/order_shipped.go @@ -14,7 +14,7 @@ func NewOrderShipped(orderID string) *OrderShipped { } func (r *OrderShipped) Via(notifiable notification.Notifiable) []string { - return []string{"mail"} + return []string{notification.ChannelMail} } func (r *OrderShipped) ToMail(notifiable notification.Notifiable) notification.MailMessage { diff --git a/app/notifications/welcome.go b/app/notifications/welcome.go index f7043dc..9fa106d 100644 --- a/app/notifications/welcome.go +++ b/app/notifications/welcome.go @@ -13,7 +13,7 @@ func NewWelcome(name string) *Welcome { } func (r *Welcome) Via(notifiable notification.Notifiable) []string { - return []string{"database"} + return []string{notification.ChannelDatabase} } func (r *Welcome) ID() string { diff --git a/go.mod b/go.mod index 249dc3c..1848a55 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/goravel/cos v1.18.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.18.0 - github.com/goravel/framework v1.18.1-0.20260805080351-d2e95f66f306 + github.com/goravel/framework v1.18.1-0.20260816093758-fcfadc23b062 github.com/goravel/gemini v1.18.0 github.com/goravel/gin v1.18.0 github.com/goravel/minio v1.18.0 @@ -29,11 +29,11 @@ require ( github.com/swaggo/swag v1.16.2 github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/vektah/gqlparser/v2 v2.5.19 - go.opentelemetry.io/otel v1.44.0 - go.opentelemetry.io/otel/metric v1.44.0 - go.opentelemetry.io/otel/trace v1.44.0 + go.opentelemetry.io/otel v1.45.0 + go.opentelemetry.io/otel/metric v1.45.0 + go.opentelemetry.io/otel/trace v1.45.0 google.golang.org/grpc v1.83.0 - google.golang.org/protobuf v1.36.11 + google.golang.org/protobuf v1.36.12 ) require ( @@ -94,13 +94,13 @@ require ( github.com/dromara/carbon/v2 v2.6.11 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/timeout v1.2.1 // indirect github.com/gin-gonic/gin v1.12.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect @@ -213,29 +213,29 @@ require ( github.com/zeebo/xxh3 v1.1.0 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 // indirect - go.opentelemetry.io/otel/log v0.20.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 // indirect + go.opentelemetry.io/otel/log v0.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.21.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.54.0 // indirect - golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 // indirect + golang.org/x/exp v0.0.0-20260810151157-a8b543ca52da // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/sync v0.22.0 // indirect @@ -245,8 +245,8 @@ require ( golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.48.0 // indirect google.golang.org/genai v1.58.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect gopkg.in/ini.v1 v1.67.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect @@ -256,4 +256,4 @@ require ( gorm.io/plugin/dbresolver v1.6.2 // indirect ) -replace github.com/goravel/framework => github.com/goravel/framework v1.18.1-0.20260805080351-d2e95f66f306 +replace github.com/goravel/framework => github.com/goravel/framework v1.18.1-0.20260816093758-fcfadc23b062 diff --git a/go.sum b/go.sum index db907cd..bc2624c 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -213,8 +213,8 @@ github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= @@ -296,8 +296,8 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg= github.com/goravel/fiber v1.18.0 h1:q9EACPCpEn+N5SX6yyI7hsdA1Zr2NFDJLdiAXQKKUw8= github.com/goravel/fiber v1.18.0/go.mod h1:swGaS9bq2wT6T6uthcHEJ2In6QyaTljsqrkRwk+wPfI= -github.com/goravel/framework v1.18.1-0.20260805080351-d2e95f66f306 h1:7asHFinzj97WSPP5jZHvq8IyMtWBdKUQXBj4lPBDokA= -github.com/goravel/framework v1.18.1-0.20260805080351-d2e95f66f306/go.mod h1:EZBVw/Le3hlnINJ619fanc7aDo5LEDM6YDrw+MmbNXA= +github.com/goravel/framework v1.18.1-0.20260816093758-fcfadc23b062 h1:oibkBVVx3ZGoqixT7Ycgf2QVH4RTlvfvnknzXBqSmpo= +github.com/goravel/framework v1.18.1-0.20260816093758-fcfadc23b062/go.mod h1:PlxHoKYYpL8ZdR6RZC6JMPO9DTQS8cU/hePRfH/tfgI= github.com/goravel/gemini v1.18.0 h1:Tv6IJ3KvBvH5QD7kAM3XFQR0g8IUfkqMbRGi7dtlWUo= github.com/goravel/gemini v1.18.0/go.mod h1:b4FueB0O0Qkz71eVWLiSaKghp+Eb/1hNNICkk7vMgrQ= github.com/goravel/gin v1.18.0 h1:4eIy9eAzH+0mz8SN03iwwocIGwc/2dpZkgz6Ol68KIE= @@ -595,54 +595,54 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/contrib/propagators/b3 v1.44.0 h1:1IFH4oFKK8KupzIelCl3u+bkxpGRps1oWRjQI2+TTWs= -go.opentelemetry.io/contrib/propagators/b3 v1.44.0/go.mod h1:JqWFXsc7VDaqIyubFhEd2cPHqsrzqP0Lvn783SUwyro= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 h1:rydZ9sxbcFdm/oWrVyfLTjHIygMgv0bEeMd+3B/BvoM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0/go.mod h1:earQ25dooT0Hhspq59DZ8YCC50jWfOlFEeWoxy/P444= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 h1:owlhcJ3QO3X0YTDTCcDZ4V+6aVDkWbNmBoQ5NUp7Oww= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0/go.mod h1:MP4eemTiI9zC8fgg+DYynhYDYf3ba72S376TvP+Ye0Q= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0 h1:aZfdmtI6QU/DAPD4b7YZ5zuJgewxO1EW9miOZklqleU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0/go.mod h1:isNl10/Om5CBWu9jj8WOb2+tJLbCVXDgqwzCaJMnJ6w= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0= -go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= -go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= -go.opentelemetry.io/otel/log/logtest v0.20.0 h1:+tsZVE15N+RWyN9lUzsRyw7hMZXNMepGu105Eim82/k= -go.opentelemetry.io/otel/log/logtest v0.20.0/go.mod h1:zS9Ryx9RrEAG2tgapMBSvacwhVSSOGSaSiWWgW3NPlQ= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY= -go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU= -go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg= -go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 h1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= +go.opentelemetry.io/contrib/propagators/b3 v1.45.0 h1:audI5r8RmWVSORhzA5Y57yGvEA1358PvGk0u0sMOTDA= +go.opentelemetry.io/contrib/propagators/b3 v1.45.0/go.mod h1:SiENIek0FnzLni3/jSCiumyCA2mwP8uGaE1686SOJug= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 h1:WseeVYf5dJZTsyPiyW5L14k5qsSibqXAMTSiFEDiWr0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0/go.mod h1:SiLZnQS6Qk2eCpvr2CH/XMAOa64TWGXxEZJZCpD2Lmc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 h1:fvNHGyo3CdRv/DQveXqhqBxnKTDyRaC5sMSQxilX/A0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0/go.mod h1:zyGrjRKL2B/6+Jc/m4/otPoZqV2MY9ZjC/aBraRO7zc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.45.0 h1:klTViGcsvLCd1xN3rZzfZ12NslC/OimbmR+k+A006RI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.45.0/go.mod h1:jRsK04CWmXuY8A0O+wMpSf+t90RHZ53o5Qmxn2PQPfk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.45.0 h1:pnxy6c/kvNBWdNNFzqpjuJLm9Hjhgk/Q0nY221rwuk0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.45.0/go.mod h1:qw6YsFapotRwoDhXRZvljzaOvCQB7UfnafEJagpN2TA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0 h1:2lpf4hnrasYIsUyEXwnTZq5lsxrMm4T2Bwb06IctAZQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0/go.mod h1:YWOW6h7jwApz9Pl76ie/izUsSPj0s2MdIlpqbPqaf3U= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0 h1:dm9iyzn6tioYZtwqaiBSU0TSI8Yu/8dTIbfG0+B49DY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0/go.mod h1:xAvxYjYK28qvt+yu4BYZ/zMmAjwMXINXD6JiMyeB8iI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo= +go.opentelemetry.io/otel/log v0.21.0 h1:SLsVDGmtyBrdw8/a2Z0bOIxou/+bN4z56GebH7T0LvA= +go.opentelemetry.io/otel/log v0.21.0/go.mod h1:iReetQrZL9Wyg84cCkOoCmqDHS5RCFfyxC7J+r8fn8g= +go.opentelemetry.io/otel/log/logtest v0.21.0 h1:/Zr/0DoraAjiX91pZMn72uSDkd7hA+jn3CPU2y+2rWY= +go.opentelemetry.io/otel/log/logtest v0.21.0/go.mod h1:dyswW/l7aXiiCAmbKlt+Eg2NUnN6p1YrHQPBiV7QrLU= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/log v0.21.0 h1:QsE7XSR0ktQdKmRKGnR+f1ObGF32WG+7MER/P9KgmYc= +go.opentelemetry.io/otel/sdk/log v0.21.0/go.mod h1:m9mApjCoD2/1QuKCAptjv+BrG9WKOvQLVdNx+iBldTo= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0 h1:X+JBBgKlswCGYsmgL0CnoUUtlE//VB345c84jYAYkdQ= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0/go.mod h1:HD1575K8e6sIFBBDd5tZB3t9DlMytWXq9FuR+Y4rfjE= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -673,8 +673,8 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+ijyQKJG1pLgwQ2PYdQM= -golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +golang.org/x/exp v0.0.0-20260810151157-a8b543ca52da h1:YKw1FZDyWyXXZcBxalRHz3CHieUKnKxanrbcO280Zwc= +golang.org/x/exp v0.0.0-20260810151157-a8b543ca52da/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -803,14 +803,14 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/genai v1.58.0 h1:MNA3ZkRyr7MnRwZ9RNZ60p4+UMKV3yYRw6pyHq4pp0U= google.golang.org/genai v1.58.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/feature/notification_test.go b/tests/feature/notification_test.go index 6f1ae29..d08465c 100644 --- a/tests/feature/notification_test.go +++ b/tests/feature/notification_test.go @@ -55,8 +55,8 @@ func (s *NotificationTestSuite) SetupTest() { // both registered and unknown channel names. func (s *NotificationTestSuite) TestFacadeResolves() { s.NotNil(facades.Notification()) - s.NotNil(facades.Notification().Channel("database")) - s.NotNil(facades.Notification().Channel("mail")) + s.NotNil(facades.Notification().Channel(notification.ChannelDatabase)) + s.NotNil(facades.Notification().Channel(notification.ChannelMail)) s.Nil(facades.Notification().Channel("slack")) } @@ -220,7 +220,7 @@ func (s *NotificationTestSuite) TestSendQueuedDatabaseNotificationWithQueueAndCo // TestOnDemandNotification covers Manager.Route + OnDemandNotifiable.Notify. func (s *NotificationTestSuite) TestOnDemandNotification() { - s.NoError(facades.Notification().Route("database", "123").Notify(notifications.NewWelcome("OnDemand"))) + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "123").Notify(notifications.NewWelcome("OnDemand"))) var rows []notificationRow s.NoError(facades.DB().Table("notifications").Get(&rows)) @@ -238,7 +238,7 @@ func (s *NotificationTestSuite) TestOnDemandChainedRouteNotifyNow() { manager.Extend(&captureChannel{name: channelName}) cn := &chainedViaNotification{channel: channelName} - s.NoError(manager.Route("database", "456").Route(channelName, "route").NotifyNow(cn)) + s.NoError(manager.Route(notification.ChannelDatabase, "456").Route(channelName, "route").NotifyNow(cn)) var rows []notificationRow s.NoError(facades.DB().Table("notifications").Get(&rows)) @@ -251,13 +251,13 @@ func (s *NotificationTestSuite) TestOnDemandChainedRouteNotifyNow() { // TestShouldSendSkipsChannel covers NotificationWithShouldSend: returning // false must skip delivery entirely (no row), true must deliver. func (s *NotificationTestSuite) TestShouldSendSkipsChannel() { - s.NoError(facades.Notification().Route("database", "789").NotifyNow(&shouldSendNotification{shouldSend: false})) + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "789").NotifyNow(&shouldSendNotification{shouldSend: false})) count, err := facades.DB().Table("notifications").Count() s.NoError(err) s.Equal(int64(0), count) - s.NoError(facades.Notification().Route("database", "789").NotifyNow(&shouldSendNotification{shouldSend: true})) + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "789").NotifyNow(&shouldSendNotification{shouldSend: true})) count, err = facades.DB().Table("notifications").Count() s.NoError(err) @@ -267,7 +267,7 @@ func (s *NotificationTestSuite) TestShouldSendSkipsChannel() { // TestAfterSendingHook covers NotificationWithAfterSending: the hook must // run after a successful channel delivery. func (s *NotificationTestSuite) TestAfterSendingHook() { - s.NoError(facades.Notification().Route("database", "1").NotifyNow(&afterSendingNotification{})) + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "1").NotifyNow(&afterSendingNotification{})) s.True(afterSendingCalled) count, err := facades.DB().Table("notifications").Count() @@ -275,21 +275,23 @@ func (s *NotificationTestSuite) TestAfterSendingHook() { s.Equal(int64(1), count) } -// TestDatabaseRoutableConnection covers DatabaseRoutable.DatabaseConnection: -// an empty connection name routes to the default connection and the row is +// TestNotificationWithDatabaseConnectionDefault covers +// NotificationWithDatabaseConnection.DatabaseConnection: an empty +// connection name routes to the default connection and the row is // persisted there. -func (s *NotificationTestSuite) TestDatabaseRoutableConnection() { - s.NoError(facades.Notification().Route("database", "101").NotifyNow(&databaseRoutableNotification{})) +func (s *NotificationTestSuite) TestNotificationWithDatabaseConnectionDefault() { + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "101").NotifyNow(&defaultConnectionNotification{})) count, err := facades.DB().Table("notifications").Count() s.NoError(err) s.Equal(int64(1), count) } -// TestDatabaseRoutableCustomConnection covers DatabaseRoutable.DatabaseConnection -// returning a non-empty name: delivery is routed to that connection and the -// default connection stays untouched. -func (s *NotificationTestSuite) TestDatabaseRoutableCustomConnection() { +// TestNotificationWithDatabaseConnectionCustomConnection covers +// NotificationWithDatabaseConnection.DatabaseConnection returning a +// non-empty name: delivery is routed to that connection and the default +// connection stays untouched. +func (s *NotificationTestSuite) TestNotificationWithDatabaseConnectionCustomConnection() { scope, err := tests.OverrideConfig(map[string]any{ "database.connections.reporting": map[string]any{ "database": filepath.Join(s.T().TempDir(), "reporting.db"), @@ -318,7 +320,7 @@ func (s *NotificationTestSuite) TestDatabaseRoutableCustomConnection() { table.Index("notifiable_type", "notifiable_id") })) - s.NoError(facades.Notification().Route("database", "101").NotifyNow(&reportingRoutableNotification{})) + s.NoError(facades.Notification().Route(notification.ChannelDatabase, "101").NotifyNow(&reportingConnectionNotification{})) count, err := facades.DB().Connection("reporting").Table("notifications").Count() s.NoError(err) @@ -368,6 +370,60 @@ func (s *NotificationTestSuite) TestSendMailNotificationMailRoutable() { s.NoError(facades.Notification().Send(&mailRoutableNotifiable{to: mailTo}, notifications.NewOrderShipped("notification-mail-routable"))) } +// TestDatabaseRoutableTypedRoute covers DatabaseRoutable.RouteNotificationForDatabase: +// the typed route is preferred over RouteNotificationFor, so the persisted +// NotifiableID comes from the typed route even when the generic route +// returns a different value. +func (s *NotificationTestSuite) TestDatabaseRoutableTypedRoute() { + s.NoError(facades.Notification().Send( + &databaseRoutableNotifiable{typed: "42", fallback: "wrong-route"}, + notifications.NewWelcome("typed"), + )) + + var rows []notificationRow + s.NoError(facades.DB().Table("notifications").Get(&rows)) + s.Require().Len(rows, 1) + s.Equal("42", rows[0].NotifiableID) +} + +// TestDatabaseRoutableFallbackToGenericRoute covers the empty-typed-route +// fallback: an empty RouteNotificationForDatabase result is not an error by +// itself — the channel falls back to RouteNotificationFor(ChannelDatabase). +func (s *NotificationTestSuite) TestDatabaseRoutableFallbackToGenericRoute() { + s.NoError(facades.Notification().Send( + &databaseRoutableNotifiable{typed: "", fallback: "43"}, + notifications.NewWelcome("fallback"), + )) + + var rows []notificationRow + s.NoError(facades.DB().Table("notifications").Get(&rows)) + s.Require().Len(rows, 1) + s.Equal("43", rows[0].NotifiableID) +} + +// TestDatabaseRoutableEmptyRouteError covers the error path: only an empty +// result from both RouteNotificationForDatabase and RouteNotificationFor is +// an error. +func (s *NotificationTestSuite) TestDatabaseRoutableEmptyRouteError() { + err := facades.Notification().Send( + &databaseRoutableNotifiable{}, + notifications.NewWelcome("empty"), + ) + s.ErrorIs(err, frameworkerrors.NotificationDatabaseEmptyRoute) +} + +// TestSendMailNotificationEmptyRoute covers the reworded +// NotificationMailEmptyRoute error: a notifiable with no mail route (and no +// MailRoutable) errors in resolveAddresses before any SMTP send, so the +// test runs even when mail.host is unset. +func (s *NotificationTestSuite) TestSendMailNotificationEmptyRoute() { + err := facades.Notification().Send( + &mailEmptyRouteNotifiable{}, + notifications.NewOrderShipped("empty-mail"), + ) + s.ErrorIs(err, frameworkerrors.NotificationMailEmptyRoute) +} + // TestCommandMakeNotification covers the make:notification command: stub // generation, already-exists behavior, nested packages and the --database // variant. @@ -392,6 +448,7 @@ func (s *NotificationTestSuite) TestCommandMakeNotification() { s.True(file.Contains(notificationPath, "func (r *"+notificationName+") Via(notifiable notification.Notifiable) []string {")) s.True(file.Contains(notificationPath, "func (r *"+notificationName+") ToMail(notifiable notification.Notifiable) notification.MailMessage {")) s.True(file.Contains(notificationPath, `"github.com/goravel/framework/notification/mail"`)) + s.True(file.Contains(notificationPath, "return []string{notification.ChannelMail}")) originalContent, err := os.ReadFile(notificationPath) s.NoError(err) @@ -414,6 +471,7 @@ func (s *NotificationTestSuite) TestCommandMakeNotification() { s.NoError(facades.Artisan().Call("--no-ansi make:notification --database " + databaseName)) s.True(file.Exists(databasePath)) s.True(file.Contains(databasePath, "func (r *"+databaseName+") ToDatabase(notifiable notification.Notifiable) map[string]any {")) + s.True(file.Contains(databasePath, "return []string{notification.ChannelDatabase}")) s.False(file.Contains(databasePath, "ToMail")) s.False(file.Contains(databasePath, `"github.com/goravel/framework/notification/mail"`)) @@ -557,7 +615,7 @@ type jobRow struct { type routedQueuedNotification struct{} func (r *routedQueuedNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database"} + return []string{notification.ChannelDatabase} } func (r *routedQueuedNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { @@ -573,7 +631,7 @@ type shouldSendNotification struct { } func (r *shouldSendNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database"} + return []string{notification.ChannelDatabase} } func (r *shouldSendNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { @@ -588,7 +646,7 @@ func (r *shouldSendNotification) ShouldSend(notifiable notification.Notifiable, type afterSendingNotification struct{} func (r *afterSendingNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database"} + return []string{notification.ChannelDatabase} } func (r *afterSendingNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { @@ -600,33 +658,34 @@ func (r *afterSendingNotification) AfterSending(notifiable notification.Notifiab return nil } -// databaseRoutableNotification implements DatabaseRoutable using the default -// connection (empty name). -type databaseRoutableNotification struct{} +// defaultConnectionNotification implements +// NotificationWithDatabaseConnection using the default connection (empty +// name). +type defaultConnectionNotification struct{} -func (r *databaseRoutableNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database"} +func (r *defaultConnectionNotification) Via(notifiable notification.Notifiable) []string { + return []string{notification.ChannelDatabase} } -func (r *databaseRoutableNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { +func (r *defaultConnectionNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { return map[string]any{"message": "routable"} } -func (r *databaseRoutableNotification) DatabaseConnection() string { return "" } +func (r *defaultConnectionNotification) DatabaseConnection() string { return "" } -// reportingRoutableNotification implements DatabaseRoutable using a custom -// "reporting" connection. -type reportingRoutableNotification struct{} +// reportingConnectionNotification implements +// NotificationWithDatabaseConnection using a custom "reporting" connection. +type reportingConnectionNotification struct{} -func (r *reportingRoutableNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database"} +func (r *reportingConnectionNotification) Via(notifiable notification.Notifiable) []string { + return []string{notification.ChannelDatabase} } -func (r *reportingRoutableNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { +func (r *reportingConnectionNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { return map[string]any{"message": "reporting"} } -func (r *reportingRoutableNotification) DatabaseConnection() string { return "reporting" } +func (r *reportingConnectionNotification) DatabaseConnection() string { return "reporting" } // captureChannel implements notification.Channel with an observable Send // side effect. @@ -657,7 +716,7 @@ type chainedViaNotification struct { } func (r *chainedViaNotification) Via(notifiable notification.Notifiable) []string { - return []string{"database", r.channel} + return []string{notification.ChannelDatabase, r.channel} } func (r *chainedViaNotification) ToDatabase(notifiable notification.Notifiable) map[string]any { @@ -683,3 +742,33 @@ func (r *mailRoutableNotifiable) RouteNotificationFor(channel string) any { func (r *mailRoutableNotifiable) RouteNotificationForMail(notification notification.Notification) map[string]string { return map[string]string{r.to: "Test User"} } + +// databaseRoutableNotifiable implements contracts/notification.DatabaseRoutable +// (typed route) in addition to Notifiable. typed is what +// RouteNotificationForDatabase returns and fallback is what +// RouteNotificationFor(ChannelDatabase) returns, so tests can pin which +// route the database channel prefers and what happens when either is empty. +type databaseRoutableNotifiable struct { + typed string + fallback any +} + +func (r *databaseRoutableNotifiable) RouteNotificationFor(channel string) any { + if channel == notification.ChannelDatabase { + return r.fallback + } + return nil +} + +func (r *databaseRoutableNotifiable) RouteNotificationForDatabase() string { + return r.typed +} + +// mailEmptyRouteNotifiable provides no mail route: RouteNotificationFor +// returns nil for every channel, so the mail channel reports an empty +// route without touching SMTP. +type mailEmptyRouteNotifiable struct{} + +func (r *mailEmptyRouteNotifiable) RouteNotificationFor(channel string) any { + return nil +}