1
0
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:
Niko Köser 2023-01-17 14:44:36 +01:00 committed by GitHub
parent 9c0a4d3f5e
commit 440d422648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 959 additions and 7 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ tmp/
.gh-docker-token
.env
coverage.out
cover.html

View File

@ -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 ./...

View File

@ -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
View File

@ -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
View File

@ -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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
package notify
// Version is the current version of the library.
const Version = "unknown"