feat: add sonarr support

This commit is contained in:
Dawid Wysokiński 2022-07-15 06:38:44 +02:00
parent 31608426e4
commit 1dd7c72864
Signed by: Kichiyaki
GPG Key ID: 1ECC5DE481BE5184
9 changed files with 257 additions and 8 deletions

View File

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

View File

@ -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)

View File

@ -8,6 +8,8 @@ import (
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
)
//go:generate counterfeiter -generate
var (
errInternal = domain.NewError(
domain.WithCode(domain.ErrorCodeUnknown),

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

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

View File

@ -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=

View File

@ -5,4 +5,5 @@ package tools
import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/maxbrunsfeld/counterfeiter/v6"
)