feat: add sonarr support
This commit is contained in:
parent
31608426e4
commit
1dd7c72864
|
@ -5,6 +5,7 @@ set -o errexit -eo pipefail
|
|||
cd ./internal/tools
|
||||
|
||||
go install \
|
||||
github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
github.com/golangci/golangci-lint/cmd/golangci-lint \
|
||||
github.com/maxbrunsfeld/counterfeiter/v6
|
||||
|
||||
cd ../..
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/rest"
|
||||
)
|
||||
|
||||
type FakeSonarrService struct {
|
||||
ProcessStub func(context.Context, domain.SonarrWebhookPayload) error
|
||||
processMutex sync.RWMutex
|
||||
processArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 domain.SonarrWebhookPayload
|
||||
}
|
||||
processReturns struct {
|
||||
result1 error
|
||||
}
|
||||
processReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) Process(arg1 context.Context, arg2 domain.SonarrWebhookPayload) error {
|
||||
fake.processMutex.Lock()
|
||||
ret, specificReturn := fake.processReturnsOnCall[len(fake.processArgsForCall)]
|
||||
fake.processArgsForCall = append(fake.processArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 domain.SonarrWebhookPayload
|
||||
}{arg1, arg2})
|
||||
stub := fake.ProcessStub
|
||||
fakeReturns := fake.processReturns
|
||||
fake.recordInvocation("Process", []interface{}{arg1, arg2})
|
||||
fake.processMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) ProcessCallCount() int {
|
||||
fake.processMutex.RLock()
|
||||
defer fake.processMutex.RUnlock()
|
||||
return len(fake.processArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) ProcessCalls(stub func(context.Context, domain.SonarrWebhookPayload) error) {
|
||||
fake.processMutex.Lock()
|
||||
defer fake.processMutex.Unlock()
|
||||
fake.ProcessStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) ProcessArgsForCall(i int) (context.Context, domain.SonarrWebhookPayload) {
|
||||
fake.processMutex.RLock()
|
||||
defer fake.processMutex.RUnlock()
|
||||
argsForCall := fake.processArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) ProcessReturns(result1 error) {
|
||||
fake.processMutex.Lock()
|
||||
defer fake.processMutex.Unlock()
|
||||
fake.ProcessStub = nil
|
||||
fake.processReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) ProcessReturnsOnCall(i int, result1 error) {
|
||||
fake.processMutex.Lock()
|
||||
defer fake.processMutex.Unlock()
|
||||
fake.ProcessStub = nil
|
||||
if fake.processReturnsOnCall == nil {
|
||||
fake.processReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.processReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.processMutex.RLock()
|
||||
defer fake.processMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeSonarrService) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ rest.SonarrService = new(FakeSonarrService)
|
|
@ -8,6 +8,8 @@ import (
|
|||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
)
|
||||
|
||||
//go:generate counterfeiter -generate
|
||||
|
||||
var (
|
||||
errInternal = domain.NewError(
|
||||
domain.WithCode(domain.ErrorCodeUnknown),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package rest
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -21,7 +21,7 @@ func doRequest(mux chi.Router, method, target string, body io.Reader) *http.Resp
|
|||
return rr.Result()
|
||||
}
|
||||
|
||||
func assertResponse(tb testing.TB, resp *http.Response, expectedStatus int, expected any, target any) {
|
||||
func assertJSONResponse(tb testing.TB, resp *http.Response, expectedStatus int, expected any, target any) {
|
||||
tb.Helper()
|
||||
|
||||
assert.Equal(tb, expectedStatus, resp.StatusCode)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
//counterfeiter:generate -o internal/mock/sonarr_service.gen.go . SonarrService
|
||||
type SonarrService interface {
|
||||
Process(ctx context.Context, payload domain.SonarrWebhookPayload) error
|
||||
}
|
||||
|
@ -87,5 +88,5 @@ func (h *WebhookHandler) handleSonarrWebhook(w http.ResponseWriter, r *http.Requ
|
|||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,131 @@
|
|||
package rest_test
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/rest"
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/rest/internal/mock"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func TestWebhookHandler_Sonarr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(svc *mock.FakeSonarrService)
|
||||
body io.Reader
|
||||
expectedStatus int
|
||||
target any
|
||||
expectedResponse any
|
||||
}{
|
||||
{
|
||||
name: "OK: event type=Download",
|
||||
setup: func(svc *mock.FakeSonarrService) {
|
||||
svc.ProcessReturns(nil)
|
||||
},
|
||||
body: func() *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
_ = json.NewEncoder(&buf).Encode(rest.SonarrWebhookRequest{
|
||||
EventType: "Download",
|
||||
Series: rest.SonarrSeries{
|
||||
ID: 1,
|
||||
Title: "Series 1",
|
||||
},
|
||||
Episodes: []rest.SonarrEpisode{
|
||||
{
|
||||
ID: 1,
|
||||
EpisodeNumber: 1,
|
||||
SeasonNumber: 1,
|
||||
Title: "Ep 1",
|
||||
},
|
||||
},
|
||||
})
|
||||
return &buf
|
||||
}(),
|
||||
expectedStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
name: "ERR: only JSON is accepted",
|
||||
setup: func(svc *mock.FakeSonarrService) {},
|
||||
body: func() *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
_ = gob.NewEncoder(&buf).Encode(rest.SonarrWebhookRequest{
|
||||
EventType: "Download",
|
||||
Series: rest.SonarrSeries{
|
||||
ID: 1,
|
||||
Title: "Series 1",
|
||||
},
|
||||
Episodes: []rest.SonarrEpisode{
|
||||
{
|
||||
ID: 1,
|
||||
EpisodeNumber: 1,
|
||||
SeasonNumber: 1,
|
||||
Title: "Ep 1",
|
||||
},
|
||||
},
|
||||
})
|
||||
return &buf
|
||||
}(),
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
target: &rest.ErrorResponse{},
|
||||
expectedResponse: &rest.ErrorResponse{
|
||||
Error: rest.APIError{
|
||||
Code: domain.ErrorCodeValidation.String(),
|
||||
Message: "invalid request body",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: unsupported event type",
|
||||
setup: func(svc *mock.FakeSonarrService) {},
|
||||
body: func() *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
_ = json.NewEncoder(&buf).Encode(rest.SonarrWebhookRequest{
|
||||
EventType: "xxxx",
|
||||
})
|
||||
return &buf
|
||||
}(),
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
target: &rest.ErrorResponse{},
|
||||
expectedResponse: &rest.ErrorResponse{
|
||||
Error: rest.APIError{
|
||||
Code: domain.ErrUnsupportedSonarrEventType.Code().String(),
|
||||
Message: domain.ErrUnsupportedSonarrEventType.Message(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := mock.FakeSonarrService{}
|
||||
if tt.setup != nil {
|
||||
tt.setup(&svc)
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
rest.NewWebhookHandler(&svc).Register(router)
|
||||
|
||||
resp := doRequest(router, http.MethodPost, "/webhook/sonarr", tt.body)
|
||||
defer assert.NoError(t, resp.Body.Close())
|
||||
if tt.target != nil && tt.expectedResponse != nil {
|
||||
assertJSONResponse(t, resp, tt.expectedStatus, tt.expectedResponse, tt.target)
|
||||
} else {
|
||||
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,10 @@ module gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/tools
|
|||
|
||||
go 1.18
|
||||
|
||||
require github.com/golangci/golangci-lint v1.46.2
|
||||
require (
|
||||
github.com/golangci/golangci-lint v1.46.2
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gochecknoglobals v0.1.0 // indirect
|
||||
|
|
|
@ -533,6 +533,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
|||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0 h1:rBhB9Rls+yb8kA4x5a/cWxOufWfXt24E+kq4YlbGj3g=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0/go.mod h1:fJ0UAZc1fx3xZhU4eSHQDJ1ApFmTVhp5VTpV9tm2ogg=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
|
||||
|
@ -573,7 +575,6 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81
|
|||
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nishanths/exhaustive v0.7.11 h1:xV/WU3Vdwh5BUH4N06JNUznb6d5zhRPOnlgCrpNYNKA=
|
||||
github.com/nishanths/exhaustive v0.7.11/go.mod h1:gX+MP7DWMKJmNa1HfMozK+u04hQd3na9i0hyqf3/dOI=
|
||||
|
@ -689,6 +690,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
|||
github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI=
|
||||
github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo=
|
||||
|
@ -1365,8 +1367,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
|||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
|
|
|
@ -5,4 +5,5 @@ package tools
|
|||
|
||||
import (
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "github.com/maxbrunsfeld/counterfeiter/v6"
|
||||
)
|
||||
|
|
Reference in New Issue