mirror of
https://github.com/nikoksr/notify.git
synced 2024-11-21 16:46:32 +02:00
feat(service): add generic HTTP service (#506)
This commit is contained in:
parent
9c0a4d3f5e
commit
440d422648
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ tmp/
|
||||
.gh-docker-token
|
||||
.env
|
||||
coverage.out
|
||||
cover.html
|
||||
|
17
Makefile
17
Makefile
@ -18,14 +18,21 @@ setup:
|
||||
# TESTS
|
||||
###############################################################################
|
||||
|
||||
# Run all the tests
|
||||
test:
|
||||
go test -failfast -race -timeout=5m ./...
|
||||
go test -failfast -race ./...
|
||||
.PHONY: test
|
||||
|
||||
cover:
|
||||
go test -race -covermode=atomic -coverprofile=coverage.out ./...
|
||||
.PHONY: cover
|
||||
gen-coverage:
|
||||
@go test -race -covermode=atomic -coverprofile=coverage.out ./... > /dev/null
|
||||
.PHONY: gen-coverage
|
||||
|
||||
coverage: gen-coverage
|
||||
go tool cover -func coverage.out
|
||||
.PHONY: coverage
|
||||
|
||||
coverage-html: gen-coverage
|
||||
go tool cover -html=coverage.out -o cover.html
|
||||
.PHONY: coverage-html
|
||||
|
||||
mock:
|
||||
go generate ./...
|
||||
|
@ -86,6 +86,7 @@ Yes, please! Contributions of all kinds are very welcome! Feel free to check our
|
||||
| [Discord](https://discord.com) | [service/discord](service/discord) | [bwmarrin/discordgo](https://github.com/bwmarrin/discordgo) | :heavy_check_mark: |
|
||||
| [Email](https://wikipedia.org/wiki/Email) | [service/mail](service/mail) | [jordan-wright/email](https://github.com/jordan-wright/email) | :heavy_check_mark: |
|
||||
| [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [service/fcm](service/fcm) | [appleboy/go-fcm](https://github.com/appleboy/go-fcm) | :heavy_check_mark: |
|
||||
| [HTTP](https://wikipedia.org/wiki/Hypertext_Transfer_Protocol) | [service/http](service/http) | - | :heavy_check_mark: |
|
||||
| [Lark](https://www.larksuite.com/) | [service/lark](service/lark) | [go-lark/lark](https://github.com/go-lark/lark) | :heavy_check_mark: |
|
||||
| [Line](https://line.me) | [service/line](service/line) | [line/line-bot-sdk-go](https://github.com/line/line-bot-sdk-go) | :heavy_check_mark: |
|
||||
| [Line Notify](https://notify-bot.line.me) | [service/line](service/line) | [utahta/go-linenotify](https://github.com/utahta/go-linenotify) | :heavy_check_mark: |
|
||||
|
2
go.mod
2
go.mod
@ -76,6 +76,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
@ -94,5 +95,6 @@ require (
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
62
go.sum
62
go.sum
@ -1,6 +1,15 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
|
||||
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
|
||||
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1 h1:OK+bCugQcr2YjyYKeDzULqCtM50TPUFM6LvQtszKfcw=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
|
||||
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
|
||||
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20220903135808-56c5346a1a28 h1:OJe0G++TYGhE525XnkrF9KF15D1WtQdyrk19SFwRrKk=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20220903135808-56c5346a1a28/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20221121042443-a3fd332d56d9 h1:vuu1KBsr6l7XU3CHsWESP/4B1SNd+VZkrgeFZsUXrsY=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20221121042443-a3fd332d56d9/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA=
|
||||
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
||||
@ -9,6 +18,34 @@ github.com/appleboy/go-fcm v0.1.5 h1:fKbcZf/7vwGsvDkcop8a+kCHnK+tt4wXX0X7uEzwI6E
|
||||
github.com/appleboy/go-fcm v0.1.5/go.mod h1:MSxZ4LqGRsnywOjnlXJXMqbjZrG4vf+0oHitfC9HRH0=
|
||||
github.com/atc0005/go-teams-notify/v2 v2.6.1 h1:t22ybzQuaQs4UJe4ceF5VYGsPhs6ir3nZOId/FBy6Go=
|
||||
github.com/atc0005/go-teams-notify/v2 v2.6.1/go.mod h1:xo6GejLDHn3tWBA181F8LrllIL0xC1uRsRxq7YNXaaY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.14 h1:db6GvO4Z2UqHt5gvT0lr6J5x5P+oQ7bdRzczVaRekMU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.14/go.mod h1:s/G+UV29dECbF5rf+RNj1xhlmvoNurGSr+McVSRj59w=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.5 h1:+NS1BWvprx7nHcIk5o32LrZgifs/7Pm1V2nWjQgZ2H0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.5/go.mod h1:H0cvPNDO3uExWts/9PDhD/0ne2esu1uaIulwn1vkwxM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.18 h1:HF62tbhARhgLfvmfwUbL9qZ+dkbZYzbFdxBb3l5gr7Q=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.18/go.mod h1:O7n/CPagQ33rfG6h7vR/W02ammuc5CrsSM22cNZp9so=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 h1:nkQ+aI0OCeYfzrBipL6ja/6VEbUnHQoZHBHtoK+Nzxw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15/go.mod h1:Oz2/qWINxIgSmoZT9adpxJy2UhpcOAI3TIyWgYMVSz0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 h1:gRIXnmAVNyoRQywdNtpAkgY+f30QNzgF53Q5OobNZZs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21/go.mod h1:XsmHMV9c512xgsW01q7H0ut+UQQQpWX8QsFbdLHDwaU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 h1:noAhOo2mMDyYhTx99aYPvQw16T3fQ/DiKAv9fzpIKH8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15/go.mod h1:kjJ4CyD9M3Wq88GYg3IPfj67Rs0Uvz8aXK7MJ8BvE4I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 h1:nF+E8HfYpOMw6M5oA9efB602VC00IHNQnB5CmFvZPvA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22/go.mod h1:tltHVGy977LrSOgRR5aV9+miyno/Gul/uJNPKS7FzP4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 h1:xlf0J6DUgAj/ocvKQxCmad8Bu1lJuRbt5Wu+4G1xw1g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15/go.mod h1:ZVJ7ejRl4+tkWMuCwjXoy0jd8fF5u3RCyWjSVjUIvQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.14.16 h1:F+1UqtImFZoJs48f4DO/usm8P+Ok5i6Ll5+T9csXXvU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.14.16/go.mod h1:ufZIF0CTaAcA/Yammf5sQSGut2kLgXEOY5rssBpi9eE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.17 h1:VKMhV1kisP1oNtCZQ2b9Aj8Hx1vwCC/bLlg2rw4tW/0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.17/go.mod h1:hygPv9etah0QZWMe7TEE+PCPe1VL+1tfwYvJZz478uc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 h1:7jUFr+7F4MzIjCZzy7ygRtXFQcQ0kAbT0gUvtUeAdyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.21/go.mod h1:q8nYq51W3gpZempYsAD83fPRlrOTMCwN+Ahg4BKFTXQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 h1:UTTPNP3/WzZa7hoHP3Szb/Yl0bM3NoBrf5ABy1OArUM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3/go.mod h1:+IF75RMJh0+zqTGXGshyEGRsU2ImqWv6UuHGkHl6kEo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 h1:LVM2jzEQ8mhb2dhrFl4PJ3sa5+KcKT01dsMk2Ma9/FU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.17/go.mod h1:bQujK1n0V1D1Gz5uII1jaB1WDvhj4/T3tElsJnVXCR0=
|
||||
github.com/aws/smithy-go v1.13.2 h1:TBLKyeJfXTrTXRHmsv4qWt9IQGYyWThLYaJWSahTOGE=
|
||||
github.com/aws/smithy-go v1.13.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.8 h1:lDpy0WM8AHsywOnVrOHaSMfpaiV2igOw8D7svkFkXVA=
|
||||
@ -56,6 +93,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/dghubble/go-twitter v0.0.0-20220816163853-8a0df96f1e6d h1:qiUGPQxwkgoeDXtYaBEioXLEHffmBsRkM/9eum0vLS4=
|
||||
github.com/dghubble/go-twitter v0.0.0-20220816163853-8a0df96f1e6d/go.mod h1:q7VYuSasPO79IE/QBNAMYVNlzZNy4Zr7vay6is50u5I=
|
||||
github.com/dghubble/oauth1 v0.7.1 h1:JjbOVSVVkms9A4h/sTQy5Jb2nFuAAVb2qVYgenJPyrE=
|
||||
github.com/dghubble/oauth1 v0.7.1/go.mod h1:0eEzON0UY/OLACQrmnjgJjmvCGXzjBCsZqL1kWDXtF0=
|
||||
github.com/dghubble/oauth1 v0.7.2 h1:pwcinOZy8z6XkNxvPmUDY52M7RDPxt0Xw1zgZ6Cl5JA=
|
||||
github.com/dghubble/oauth1 v0.7.2/go.mod h1:9erQdIhqhOHG/7K9s/tgh9Ks/AfoyrO5mW/43Lu2+kE=
|
||||
github.com/dghubble/sling v1.4.0 h1:/n8MRosVTthvMbwlNZgLx579OGVjUOy3GNEv5BIqAWY=
|
||||
@ -77,6 +118,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-lark/lark v1.7.3 h1:02PupA3383fc+Ym/nOhZC8g7OaYrDzXzl0zjDpgOR1k=
|
||||
github.com/go-lark/lark v1.7.3/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
|
||||
github.com/go-lark/lark v1.7.4 h1:W+uMqLVnvadDuTrTx1LZUDftycYcLfeedvHtSuge40I=
|
||||
github.com/go-lark/lark v1.7.4/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
@ -145,9 +188,14 @@ github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 h1:K8qael4Lems
|
||||
github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7/go.mod h1:/Pk5i/SqYdYv1cie5wGwoZ4P6TpgMi+Yf58mtJSHdOw=
|
||||
github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c h1:hnbwWED5rIu+UaMkLR3JtnscMVGqp35lfzQwLuZAAUY=
|
||||
github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20220615032439-b0fe9b151b0e h1:2HUamy+op/UxwJxDIg19oy/tIO/2M2tSasvihvhex4s=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20220615032439-b0fe9b151b0e/go.mod h1:PDdDH7RSKjjy9iFyoMzfeChOSmXpXuMEUqmAJSihxx4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20221122012537-65f3dd7539e2 h1:k+lYMvS9cAl7e4Ea78qodfa6QZfXNa4QlFS/0GYpanI=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20221122012537-65f3dd7539e2/go.mod h1:PDdDH7RSKjjy9iFyoMzfeChOSmXpXuMEUqmAJSihxx4=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
|
||||
@ -178,15 +226,20 @@ github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/plivo/plivo-go/v7 v7.11.0 h1:nOaRybcTdCJc5C0apDTSbiGTUgp3NIy8r9aGuR7uUoE=
|
||||
github.com/plivo/plivo-go/v7 v7.11.0/go.mod h1:Jw1e16x0WjW334botVeKQT4hcMXMN55r4HH0XoGsR6Q=
|
||||
github.com/plivo/plivo-go/v7 v7.16.0 h1:xxVv4cSCaHapwd0UmFXE3q9M1fOGpeRzEa+0hj3M6h4=
|
||||
github.com/plivo/plivo-go/v7 v7.16.0/go.mod h1:Jw1e16x0WjW334botVeKQT4hcMXMN55r4HH0XoGsR6Q=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||
github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
|
||||
@ -269,6 +322,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -317,8 +372,11 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
|
78
service/http/README.md
Normal file
78
service/http/README.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Generic HTTP service
|
||||
|
||||
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/nikoksr/notify/service/http)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Technically, you don't need any prerequisites to use this service. However, you will need an HTTP endpoint to send requests to.
|
||||
|
||||
See our [Notify Test Server](https://github.com/nikoksr/notify-http-test) for a simple HTTP server that can be used for testing.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
stdhttp "net/http"
|
||||
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create http service
|
||||
httpService := http.New()
|
||||
|
||||
//
|
||||
// In the following example, we will send two requests to the same HTTP endpoint. This is meant to be used with
|
||||
// Notify's test http server: https://github.com/nikoksr/notify-http-test. It supports multiple content-types on the
|
||||
// same endpoint, to simplify testing. All you have to do is run `go run main.go` in the test server's directory and
|
||||
// this example will work.
|
||||
// So the following example should send two requests to the same endpoint, one with content-type application/json and
|
||||
// one with content-type text/plain. The requests should be logged differently by the test server since we provide
|
||||
// a custom payload builder func for the second webhook.
|
||||
|
||||
// Add a default webhook; this uses application/json as content type and POST as request method.
|
||||
httpService.AddReceiversURLs("http://localhost:8080")
|
||||
|
||||
// Add a custom webhook; the build payload function is used to build the payload that will be sent to the receiver
|
||||
// from the given subject and message.
|
||||
httpService.AddReceivers(&http.Webhook{
|
||||
URL: "http://localhost:8080",
|
||||
ContentType: "text/plain",
|
||||
Method: stdhttp.MethodPost,
|
||||
BuildPayload: func(subject, message string) (payload any) {
|
||||
return "[text/plain]: " + subject + " - " + message
|
||||
},
|
||||
})
|
||||
|
||||
//
|
||||
// NOTE: In case of an unsupported content type, we could provide a custom marshaller here.
|
||||
// See http.Service.Serializer and http.defaultMarshaller.Marshal for details.
|
||||
//
|
||||
|
||||
// Add pre-send hook to log the request before it is sent.
|
||||
httpService.PreSend(func(ctx context.Context, req *stdhttp.Request) error {
|
||||
log.Printf("Sending request to %s", req.URL)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add post-send hook to log the response after it is received.
|
||||
httpService.PostSend(func(ctx context.Context, req *stdhttp.Request, resp *stdhttp.Response) error {
|
||||
log.Printf("Received response from %s", resp.Request.URL)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Create the notifier and use the HTTP service
|
||||
n := notify.NewWithServices(httpService)
|
||||
|
||||
// Send a test message.
|
||||
if err := n.Send(context.Background(), "Testing new features", "Notify's HTTP service is here."); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
76
service/http/doc.go
Normal file
76
service/http/doc.go
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Package http provides an HTTP service. It is used to send notifications to HTTP endpoints. The service is configured
|
||||
with a list of webhooks. Each webhook contains the URL of the endpoint, the HTTP method to use, the content type of the
|
||||
HTTP request and a function that builds the payload of the request.
|
||||
|
||||
The service also allows to register pre and post send hooks. These hooks are called before and after the request is sent
|
||||
to the receiver. The pre send hook can be used to modify the request before it is sent. The post send hook can be used to
|
||||
modify the response after it is received. The hooks are called in the order they are registered.
|
||||
|
||||
Usage:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
stdhttp "net/http"
|
||||
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create http service
|
||||
httpService := http.New()
|
||||
|
||||
//
|
||||
// In the following example, we will send two requests to the same HTTP endpoint. This is meant to be used with
|
||||
// Notify's test http server: https://github.com/nikoksr/notify-http-test. It supports multiple content-types on the
|
||||
// same endpoint, to simplify testing. All you have to do is run `go run main.go` in the test server's directory and
|
||||
// this example will work.
|
||||
// So the following example should send two requests to the same endpoint, one with content-type application/json and
|
||||
// one with content-type text/plain. The requests should be logged differently by the test server since we provide
|
||||
// a custom payload builder func for the second webhook.
|
||||
|
||||
// Add a default webhook; this uses application/json as content type and POST as request method.
|
||||
httpService.AddReceiversURLs("http://localhost:8080")
|
||||
|
||||
// Add a custom webhook; the build payload function is used to build the payload that will be sent to the receiver
|
||||
// from the given subject and message.
|
||||
httpService.AddReceivers(&http.Webhook{
|
||||
URL: "http://localhost:8080",
|
||||
ContentType: "text/plain",
|
||||
Method: stdhttp.MethodPost,
|
||||
BuildPayload: func(subject, message string) (payload any) {
|
||||
return "[text/plain]: " + subject + " - " + message
|
||||
},
|
||||
})
|
||||
|
||||
//
|
||||
// NOTE: In case of an unsupported content type, we could provide a custom marshaller here.
|
||||
// See http.Service.Serializer and http.defaultMarshaller.Marshal for details.
|
||||
//
|
||||
|
||||
// Add pre-send hook to log the request before it is sent.
|
||||
httpService.PreSend(func(ctx context.Context, req *stdhttp.Request) error {
|
||||
log.Printf("Sending request to %s", req.URL)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add post-send hook to log the response after it is received.
|
||||
httpService.PostSend(func(ctx context.Context, req *stdhttp.Request, resp *stdhttp.Response) error {
|
||||
log.Printf("Received response from %s", resp.Request.URL)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Create the notifier and use the HTTP service
|
||||
n := notify.NewWithServices(httpService)
|
||||
|
||||
// Send a test message.
|
||||
if err := n.Send(context.Background(), "Testing new features", "Notify's HTTP service is here."); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
package http
|
279
service/http/http.go
Normal file
279
service/http/http.go
Normal file
@ -0,0 +1,279 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/nikoksr/notify"
|
||||
)
|
||||
|
||||
type (
|
||||
// PreSendHookFn defines a function signature for a pre-send hook.
|
||||
PreSendHookFn func(req *http.Request) error
|
||||
|
||||
// PostSendHookFn defines a function signature for a post-send hook.
|
||||
PostSendHookFn func(req *http.Request, resp *http.Response) error
|
||||
|
||||
// BuildPayloadFn defines a function signature for a function that builds a payload.
|
||||
BuildPayloadFn func(subject, message string) (payload any)
|
||||
|
||||
// Serializer is used to serialize the payload to a byte slice.
|
||||
Serializer interface {
|
||||
Marshal(contentType string, payload any) (payloadRaw []byte, err error)
|
||||
}
|
||||
|
||||
// Webhook represents a single webhook receiver. It contains all the information needed to send a valid request to
|
||||
// the receiver. The BuildPayload function is used to build the payload that will be sent to the receiver from the
|
||||
// given subject and message.
|
||||
Webhook struct {
|
||||
ContentType string
|
||||
Header http.Header
|
||||
Method string
|
||||
URL string
|
||||
BuildPayload BuildPayloadFn
|
||||
}
|
||||
|
||||
// Service is the main struct of this package. It contains all the information needed to send notifications to a
|
||||
// list of receivers. The receivers are represented by Webhooks and are expected to be valid HTTP endpoints. The
|
||||
// Service also allows
|
||||
Service struct {
|
||||
client *http.Client
|
||||
webhooks []*Webhook
|
||||
preSendHooks []PreSendHookFn
|
||||
postSendHooks []PostSendHookFn
|
||||
Serializer Serializer
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "notify/" + notify.Version
|
||||
defaultContentType = "application/json; charset=utf-8"
|
||||
defaultRequestMethod = http.MethodPost
|
||||
|
||||
// Defining these as constants for testing purposes.
|
||||
defaultSubjectKey = "subject"
|
||||
defaultMessageKey = "message"
|
||||
)
|
||||
|
||||
type defaultMarshaller struct{}
|
||||
|
||||
// Marshal takes a payload and serializes it to a byte slice. The content type is used to determine the serialization
|
||||
// format. If the content type is not supported, an error is returned. The default marshaller supports the following
|
||||
// content types: application/json, text/plain.
|
||||
// NOTE: should we expand the default marshaller to support more content types?
|
||||
func (defaultMarshaller) Marshal(contentType string, payload any) (out []byte, err error) {
|
||||
switch {
|
||||
case strings.HasPrefix(contentType, "application/json"):
|
||||
out, err = json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshal json")
|
||||
}
|
||||
case strings.HasPrefix(contentType, "text/plain"):
|
||||
str, ok := payload.(string)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("payload was expected to be string, but was %T", payload)
|
||||
}
|
||||
out = []byte(str)
|
||||
default:
|
||||
return nil, errors.New("unsupported content type")
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// buildDefaultPayload is the default payload builder. It builds a payload that is a map with the keys "subject" and
|
||||
// "message".
|
||||
func buildDefaultPayload(subject, message string) any {
|
||||
return map[string]string{
|
||||
defaultSubjectKey: subject,
|
||||
defaultMessageKey: message,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new instance of a Service notification service. Parameter 'tag' is used as a log prefix and may be left
|
||||
// empty, it has a fallback value.
|
||||
func New() *Service {
|
||||
return &Service{
|
||||
client: http.DefaultClient,
|
||||
webhooks: []*Webhook{},
|
||||
preSendHooks: []PreSendHookFn{},
|
||||
postSendHooks: []PostSendHookFn{},
|
||||
Serializer: defaultMarshaller{},
|
||||
}
|
||||
}
|
||||
|
||||
func newWebhook(url string) *Webhook {
|
||||
return &Webhook{
|
||||
ContentType: defaultContentType,
|
||||
Header: http.Header{},
|
||||
Method: defaultRequestMethod,
|
||||
URL: url,
|
||||
BuildPayload: buildDefaultPayload,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the webhook. It implements the fmt.Stringer interface.
|
||||
func (w *Webhook) String() string {
|
||||
if w == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(fmt.Sprintf("%s %s %s", strings.ToUpper(w.Method), w.URL, w.ContentType))
|
||||
}
|
||||
|
||||
// AddReceivers accepts a list of Webhooks and adds them as receivers. The Webhooks are expected to be valid HTTP
|
||||
// endpoints.
|
||||
func (s *Service) AddReceivers(webhooks ...*Webhook) {
|
||||
s.webhooks = append(s.webhooks, webhooks...)
|
||||
}
|
||||
|
||||
// AddReceiversURLs accepts a list of URLs and adds them as receivers. Internally it converts the URLs to Webhooks by
|
||||
// using the default content-type ("application/json") and request method ("POST").
|
||||
func (s *Service) AddReceiversURLs(urls ...string) {
|
||||
for _, url := range urls {
|
||||
s.AddReceivers(newWebhook(url))
|
||||
}
|
||||
}
|
||||
|
||||
// WithClient sets the http client to be used for sending requests. Calling this method is optional, the default client
|
||||
// will be used if this method is not called.
|
||||
func (s *Service) WithClient(client *http.Client) {
|
||||
if client != nil {
|
||||
s.client = client
|
||||
}
|
||||
}
|
||||
|
||||
// doPreSendHooks executes all the pre-send hooks. If any of the hooks returns an error, the execution is stopped and
|
||||
// the error is returned.
|
||||
func (s *Service) doPreSendHooks(req *http.Request) error {
|
||||
for _, hook := range s.preSendHooks {
|
||||
if err := hook(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doPostSendHooks executes all the post-send hooks. If any of the hooks returns an error, the execution is stopped and
|
||||
// the error is returned.
|
||||
func (s *Service) doPostSendHooks(req *http.Request, resp *http.Response) error {
|
||||
for _, hook := range s.postSendHooks {
|
||||
if err := hook(req, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PreSend adds a pre-send hook to the service. The hook will be executed before sending a request to a receiver.
|
||||
func (s *Service) PreSend(hook PreSendHookFn) {
|
||||
s.preSendHooks = append(s.preSendHooks, hook)
|
||||
}
|
||||
|
||||
// PostSend adds a post-send hook to the service. The hook will be executed after sending a request to a receiver.
|
||||
func (s *Service) PostSend(hook PostSendHookFn) {
|
||||
s.postSendHooks = append(s.postSendHooks, hook)
|
||||
}
|
||||
|
||||
// newRequest creates a new http request with the given method, content-type, url and payload. Request created by this
|
||||
// function will usually be passed to the Service.do method.
|
||||
func newRequest(ctx context.Context, hook *Webhook, payload io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, hook.Method, hook.URL, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = hook.Header
|
||||
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
}
|
||||
if req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", hook.ContentType)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// do sends the given request and returns an error if the request failed. A failed request gets identified by either
|
||||
// an unsuccessful status code or a non-nil error. The given request is expected to be valid and was usually created
|
||||
// by the newRequest function.
|
||||
func (s *Service) do(req *http.Request) error {
|
||||
// Execute all pre-send hooks in order.
|
||||
if err := s.doPreSendHooks(req); err != nil {
|
||||
return errors.Wrap(err, "pre-send hooks")
|
||||
}
|
||||
|
||||
// Actually send the HTTP request.
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Execute all post-send hooks in order.
|
||||
if err = s.doPostSendHooks(req, resp); err != nil {
|
||||
return errors.Wrap(err, "post-send hooks")
|
||||
}
|
||||
|
||||
// Check if response code is 2xx. Should this be configurable?
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("responded with status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// send is a helper method that sends a message to a single webhook. It wraps the core logic of the Send method, which
|
||||
// is creating a new request for the given webhook and sending it.
|
||||
func (s *Service) send(ctx context.Context, webhook *Webhook, payload []byte) error {
|
||||
// Create a new HTTP request for the given webhook.
|
||||
req, err := newRequest(ctx, webhook, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create request %q", webhook)
|
||||
}
|
||||
defer func() { _ = req.Body.Close() }()
|
||||
|
||||
return s.do(req)
|
||||
}
|
||||
|
||||
// Send takes a message and sends it to all webhooks.
|
||||
func (s *Service) Send(ctx context.Context, subject, message string) error {
|
||||
// Send message to all webhooks.
|
||||
for _, webhook := range s.webhooks {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// Skip webhook if it is nil.
|
||||
if webhook == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the payload for the current webhook.
|
||||
payload := webhook.BuildPayload(subject, message)
|
||||
|
||||
// Marshal the message into a payload.
|
||||
payloadRaw, err := s.Serializer.Marshal(webhook.ContentType, payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal payload")
|
||||
}
|
||||
|
||||
// Send the payload to the webhook.
|
||||
if err = s.send(ctx, webhook, payloadRaw); err != nil {
|
||||
return errors.Wrapf(err, "send request %q", webhook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
446
service/http/http_test.go
Normal file
446
service/http/http_test.go
Normal file
@ -0,0 +1,446 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Set up a test server to handle the requests
|
||||
var notifyServer *httptest.Server
|
||||
|
||||
// Allows us to simulate an error returned from the server on a per-request basis
|
||||
const headerTestError = "X-Test-Error"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var notifyHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Header.Get(headerTestError) == "true":
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
default:
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
notifyServer = httptest.NewServer(notifyHandler)
|
||||
defer notifyServer.Close()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// Create a custom serializer that will return an error
|
||||
type errorSerializer struct{}
|
||||
|
||||
// Marshal is a no-op and always returns an error.
|
||||
func (errorSerializer) Marshal(_ string, _ any) (payloadRaw []byte, err error) {
|
||||
return nil, errors.New("error")
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s1 := New()
|
||||
assert.NotNil(t, s1, "service should not be nil")
|
||||
|
||||
s2 := New()
|
||||
assert.NotNil(t, s2, "service should not be nil")
|
||||
assert.Equal(t, s1, s2, "services should be equal")
|
||||
}
|
||||
|
||||
func TestService_WithClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service := New()
|
||||
assert.NotNil(t, service, "service should not be nil")
|
||||
assert.NotNil(t, service.client, "client should not be nil")
|
||||
|
||||
// Create a new client
|
||||
client := &http.Client{}
|
||||
service.WithClient(client)
|
||||
assert.Equal(t, client, service.client, "clients should be equal")
|
||||
|
||||
// Nil client should not change the service client
|
||||
service.WithClient(nil)
|
||||
assert.Equal(t, client, service.client, "clients should be equal")
|
||||
}
|
||||
|
||||
func TestService_AddReceivers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
service *Service
|
||||
urls []string
|
||||
}{
|
||||
{
|
||||
name: "test case 1",
|
||||
service: New(),
|
||||
urls: []string{
|
||||
"http://localhost:8080",
|
||||
"http://localhost:8081",
|
||||
"http://localhost:8082",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test case 2",
|
||||
service: New(),
|
||||
urls: []string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt.service.AddReceiversURLs(tt.urls...)
|
||||
assert.Equal(t, len(tt.urls), len(tt.service.webhooks), "webhooks should be equal")
|
||||
|
||||
for i, hook := range tt.urls {
|
||||
assert.Equal(t, hook, tt.service.webhooks[i].URL, "webhooks should be equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Hooks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Set the local server as the receiver
|
||||
service := New()
|
||||
service.AddReceiversURLs(notifyServer.URL)
|
||||
|
||||
// Constants for the test
|
||||
const (
|
||||
testSubject = "test subject"
|
||||
testMessage = "test message"
|
||||
)
|
||||
|
||||
// Add a very simple pre-send hook. We'll check if the header and body are set correctly.
|
||||
service.PreSend(func(req *http.Request) error {
|
||||
// At this point, the request should be unmodified as this is the first hook. Unmarshal the bodyRaw and check the
|
||||
// subject and message.
|
||||
bodyRaw, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read request body")
|
||||
}
|
||||
|
||||
var body map[string]string
|
||||
if err := json.Unmarshal(bodyRaw, &body); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal request body")
|
||||
}
|
||||
|
||||
// This implicitly checks the correctness of buildDefaultPayload.
|
||||
assert.Equal(t, testSubject, body[defaultSubjectKey], "subject should be equal")
|
||||
assert.Equal(t, testMessage, body[defaultMessageKey], "message should be equal")
|
||||
|
||||
// Injecting new headers and bodyRaw
|
||||
req.Header.Set("X-Test-1", "test-header")
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
req.Body = io.NopCloser(bytes.NewBuffer([]byte("test-body")))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Adding a second pre-send hook. We'll check if the header and body have been correctly modified by the first hook.
|
||||
service.PreSend(func(req *http.Request) error {
|
||||
// Check the headers
|
||||
assert.Equal(t, "test-header", req.Header.Get("X-Test-1"), "header should be equal")
|
||||
assert.Equal(t, "text/plain", req.Header.Get("Content-Type"), "header should be equal")
|
||||
|
||||
// Check the bodyRaw
|
||||
bodyRaw, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read request bodyRaw")
|
||||
}
|
||||
assert.Equal(t, "test-body", string(bodyRaw), "body should be equal")
|
||||
|
||||
// Make sure the body is reset to the original value
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(bodyRaw))
|
||||
|
||||
// Also, refresh the Content-Length header. This is required because we've modified the bodyRaw and the test
|
||||
// would fail otherwise.
|
||||
req.ContentLength = int64(len(bodyRaw))
|
||||
|
||||
// Injecting a new header to confirm that consecutive hooks work as expected
|
||||
req.Header.Set("X-Test-2", "test-header-2")
|
||||
req.Header.Del("X-Test-1")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Adding a third pre-send hook. We'll check if the header and body have been correctly modified by the first two hooks.
|
||||
service.PreSend(func(req *http.Request) error {
|
||||
assert.Equal(t, "test-header-2", req.Header.Get("X-Test-2"), "header should be equal")
|
||||
assert.Equal(t, "", req.Header.Get("X-Test-1"), "header should be equal")
|
||||
|
||||
// Modifying the headers one last time to verify that the post-send hook works as expected
|
||||
req.Header.Set("X-Test-3", "test-header-3")
|
||||
req.Header.Del("X-Test-2")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add a very simple post-send hook. We'll inject a custom header and return an error, in case the according http
|
||||
// header has been set.
|
||||
service.PostSend(func(req *http.Request, res *http.Response) error {
|
||||
res.Header.Set("X-Test-1", "test-header")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add a second post-send hook. We'll check if the header has been correctly modified by the first hook.
|
||||
service.PostSend(func(req *http.Request, res *http.Response) error {
|
||||
assert.Equal(t, "test-header", res.Header.Get("X-Test-1"), "header should be equal")
|
||||
|
||||
// Injecting a new header to confirm that consecutive hooks work as expected
|
||||
res.Header.Set("X-Test-2", "test-header-2")
|
||||
res.Header.Del("X-Test-1")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add a third post-send hook. We'll check if the header has been correctly modified by the first two hooks.
|
||||
service.PostSend(func(req *http.Request, res *http.Response) error {
|
||||
assert.Equal(t, "test-header-2", res.Header.Get("X-Test-2"), "header should be equal")
|
||||
assert.Equal(t, "", res.Header.Get("X-Test-1"), "header should be equal")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Sanity check
|
||||
assert.Equal(t, 3, len(service.preSendHooks), "preSendHooks should be equal")
|
||||
assert.Equal(t, 3, len(service.postSendHooks), "postSendHooks should be equal")
|
||||
|
||||
// Send a notification
|
||||
err := service.Send(context.Background(), testSubject, testMessage)
|
||||
assert.NoError(t, err, "error should be nil")
|
||||
|
||||
// Now, add a new pre-send hook that sets special header that requests the server to return an error. We'll check if
|
||||
// the error is correctly returned.
|
||||
service.PreSend(func(req *http.Request) error {
|
||||
req.Header.Set(headerTestError, "true")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send a notification
|
||||
err = service.Send(context.Background(), testSubject, testMessage)
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
|
||||
// Reset the hooks
|
||||
service.preSendHooks = make([]PreSendHookFn, 0)
|
||||
service.postSendHooks = make([]PostSendHookFn, 0)
|
||||
|
||||
// Add a pre-send hook that returns an error
|
||||
service.PreSend(func(req *http.Request) error {
|
||||
return errors.New("test error")
|
||||
})
|
||||
|
||||
// Send a notification
|
||||
err = service.Send(context.Background(), testSubject, testMessage)
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
|
||||
// Reset the hooks again and add a post-send hook that returns an error
|
||||
service.preSendHooks = make([]PreSendHookFn, 0)
|
||||
|
||||
service.PostSend(func(req *http.Request, res *http.Response) error {
|
||||
return errors.New("test error")
|
||||
})
|
||||
|
||||
// Send a notification
|
||||
err = service.Send(context.Background(), testSubject, testMessage)
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
}
|
||||
|
||||
func TestService_Send(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create service with local server as receiver
|
||||
service := New()
|
||||
service.AddReceiversURLs(notifyServer.URL)
|
||||
|
||||
// Sending this notification should work without any issues
|
||||
err := service.Send(ctx, "test subject", "test message")
|
||||
assert.NoError(t, err, "error should be nil")
|
||||
|
||||
// Now, let's reset the receivers and set a custom one, specifically requesting for our test server to return an
|
||||
// error. This should result in an error.
|
||||
service.webhooks = make([]*Webhook, 0)
|
||||
|
||||
header := http.Header{}
|
||||
header.Set(headerTestError, "true")
|
||||
|
||||
service.AddReceivers(&Webhook{
|
||||
ContentType: defaultContentType,
|
||||
Header: header,
|
||||
Method: http.MethodPost,
|
||||
URL: notifyServer.URL,
|
||||
BuildPayload: buildDefaultPayload,
|
||||
})
|
||||
|
||||
err = service.Send(ctx, "test subject", "test message")
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
|
||||
// Reset again, add a functioning receiver again for further tests
|
||||
service.webhooks = make([]*Webhook, 0)
|
||||
service.AddReceiversURLs(notifyServer.URL)
|
||||
|
||||
// Since we won't reset the receivers list again, add a nil receiver to make sure that the service doesn't crash.
|
||||
service.AddReceivers(nil)
|
||||
|
||||
err = service.Send(ctx, "test subject", "test message")
|
||||
assert.NoError(t, err, "error should be nil")
|
||||
|
||||
// Test setting a custom marshaller that always returns an error
|
||||
service.Serializer = errorSerializer{}
|
||||
|
||||
err = service.Send(ctx, "test subject", "test message")
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
|
||||
// Test context cancellation.
|
||||
cancel() // Cancel the context
|
||||
|
||||
err = service.Send(ctx, "test subject", "test message")
|
||||
assert.Error(t, err, "error should not be nil")
|
||||
}
|
||||
|
||||
func Test_newWebhook(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hook1 := newWebhook("https://example.com")
|
||||
assert.NotNil(t, hook1, "hook1 should not be nil")
|
||||
|
||||
hook2 := newWebhook("https://example.com")
|
||||
assert.NotNil(t, hook2, "hook2 should not be nil")
|
||||
|
||||
assert.NotEqual(t, hook1, hook2, "hooks should not be equal")
|
||||
}
|
||||
|
||||
func TestWebhook_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
hook *Webhook
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
hook: &Webhook{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
hook: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "test case 1",
|
||||
hook: newWebhook("https://example.com"),
|
||||
want: "POST https://example.com application/json; charset=utf-8",
|
||||
},
|
||||
{
|
||||
name: "test case 2",
|
||||
hook: &Webhook{
|
||||
Method: http.MethodGet, // Doesn't have to make sense, but it's just for testing
|
||||
URL: "https://example.com",
|
||||
ContentType: "text/plain",
|
||||
},
|
||||
want: "GET https://example.com text/plain",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equalf(t, tt.want, tt.hook.String(), "String() = %v, want %v", tt.hook.String(), tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_defaultMarshaller_Marshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
contentType string
|
||||
payload any
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantOut []byte
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "test marshal valid json",
|
||||
args: args{
|
||||
contentType: "application/json",
|
||||
payload: map[string]interface{}{"test": "test"},
|
||||
},
|
||||
wantOut: []byte(`{"test":"test"}`),
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "test marshal invalid json",
|
||||
args: args{
|
||||
contentType: "application/json",
|
||||
payload: map[string]interface{}{"test": make(chan int)},
|
||||
},
|
||||
wantOut: nil,
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "test marshal valid text",
|
||||
args: args{
|
||||
contentType: "text/plain",
|
||||
payload: "test",
|
||||
},
|
||||
wantOut: []byte("test"),
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "test marshal invalid text",
|
||||
args: args{
|
||||
contentType: "text/plain",
|
||||
payload: map[string]interface{}{"test": "test"},
|
||||
},
|
||||
wantOut: nil,
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "test marshal invalid content type",
|
||||
args: args{
|
||||
contentType: "invalid",
|
||||
payload: map[string]interface{}{"test": "test"},
|
||||
},
|
||||
wantOut: nil,
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serializer := defaultMarshaller{}
|
||||
gotOut, err := serializer.Marshal(tt.args.contentType, tt.args.payload)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("Marshal(%v, %v)", tt.args.contentType, tt.args.payload)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.wantOut, gotOut, "Marshal(%v, %v)", tt.args.contentType, tt.args.payload)
|
||||
})
|
||||
}
|
||||
}
|
4
version.go
Normal file
4
version.go
Normal file
@ -0,0 +1,4 @@
|
||||
package notify
|
||||
|
||||
// Version is the current version of the library.
|
||||
const Version = "unknown"
|
Loading…
Reference in New Issue
Block a user