feat: add sonarr support
This commit is contained in:
parent
08b899e620
commit
e9f35e9bf4
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.18
|
|||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
)
|
||||
|
||||
|
|
2
go.sum
2
go.sum
|
@ -5,6 +5,8 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
|||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
|
|
@ -4,6 +4,7 @@ type SonarrEventType string
|
|||
|
||||
const (
|
||||
SonarrEventTypeDownload SonarrEventType = "Download"
|
||||
SonarrEventTypeTest SonarrEventType = "Test"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -11,11 +12,14 @@ var (
|
|||
)
|
||||
|
||||
func NewSonarrEventType(s string) (SonarrEventType, error) {
|
||||
if s != SonarrEventTypeDownload.String() {
|
||||
conv := SonarrEventType(s)
|
||||
switch conv {
|
||||
case SonarrEventTypeDownload,
|
||||
SonarrEventTypeTest:
|
||||
return conv, nil
|
||||
default:
|
||||
return "", ErrUnsupportedSonarrEventType
|
||||
}
|
||||
|
||||
return SonarrEventTypeDownload, nil
|
||||
}
|
||||
|
||||
func (s SonarrEventType) String() string {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/service"
|
||||
)
|
||||
|
||||
type FakePublisher struct {
|
||||
PublishStub func(context.Context, string, string) error
|
||||
publishMutex sync.RWMutex
|
||||
publishArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 string
|
||||
}
|
||||
publishReturns struct {
|
||||
result1 error
|
||||
}
|
||||
publishReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) Publish(arg1 context.Context, arg2 string, arg3 string) error {
|
||||
fake.publishMutex.Lock()
|
||||
ret, specificReturn := fake.publishReturnsOnCall[len(fake.publishArgsForCall)]
|
||||
fake.publishArgsForCall = append(fake.publishArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 string
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.PublishStub
|
||||
fakeReturns := fake.publishReturns
|
||||
fake.recordInvocation("Publish", []interface{}{arg1, arg2, arg3})
|
||||
fake.publishMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) PublishCallCount() int {
|
||||
fake.publishMutex.RLock()
|
||||
defer fake.publishMutex.RUnlock()
|
||||
return len(fake.publishArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) PublishCalls(stub func(context.Context, string, string) error) {
|
||||
fake.publishMutex.Lock()
|
||||
defer fake.publishMutex.Unlock()
|
||||
fake.PublishStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) PublishArgsForCall(i int) (context.Context, string, string) {
|
||||
fake.publishMutex.RLock()
|
||||
defer fake.publishMutex.RUnlock()
|
||||
argsForCall := fake.publishArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) PublishReturns(result1 error) {
|
||||
fake.publishMutex.Lock()
|
||||
defer fake.publishMutex.Unlock()
|
||||
fake.PublishStub = nil
|
||||
fake.publishReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) PublishReturnsOnCall(i int, result1 error) {
|
||||
fake.publishMutex.Lock()
|
||||
defer fake.publishMutex.Unlock()
|
||||
fake.PublishStub = nil
|
||||
if fake.publishReturnsOnCall == nil {
|
||||
fake.publishReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.publishReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.publishMutex.RLock()
|
||||
defer fake.publishMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakePublisher) 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 _ service.Publisher = new(FakePublisher)
|
|
@ -0,0 +1,10 @@
|
|||
package service
|
||||
|
||||
import "context"
|
||||
|
||||
//go:generate counterfeiter -generate
|
||||
|
||||
//counterfeiter:generate -o internal/mock/publisher.gen.go . Publisher
|
||||
type Publisher interface {
|
||||
Publish(ctx context.Context, title, message string) error
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
)
|
||||
|
||||
type Sonarr struct {
|
||||
publisher Publisher
|
||||
}
|
||||
|
||||
func NewSonaar(publisher Publisher) *Sonarr {
|
||||
return &Sonarr{
|
||||
publisher: publisher,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sonarr) Process(ctx context.Context, payload domain.SonarrWebhookPayload) error {
|
||||
for _, ep := range payload.Episodes {
|
||||
title, err := s.buildTitle(payload.EventType, payload.Series, ep)
|
||||
if err != nil {
|
||||
return fmt.Errorf("buildTitle: %w", err)
|
||||
}
|
||||
|
||||
msg, err := s.buildMessage(payload.EventType, payload.Series, ep)
|
||||
if err != nil {
|
||||
return fmt.Errorf("buildMessage: %w", err)
|
||||
}
|
||||
|
||||
if err := s.publisher.Publish(ctx, title, msg); err != nil {
|
||||
return fmt.Errorf("Publisher.Publish: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sonarr) buildTitle(evType domain.SonarrEventType, series domain.SonarrSeries, _ domain.SonarrEpisode) (string, error) {
|
||||
switch evType {
|
||||
case domain.SonarrEventTypeDownload,
|
||||
domain.SonarrEventTypeTest:
|
||||
return series.Title + " - New episode (Sonarr)", nil
|
||||
default:
|
||||
return "", domain.ErrUnsupportedSonarrEventType
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sonarr) buildMessage(evType domain.SonarrEventType, _ domain.SonarrSeries, ep domain.SonarrEpisode) (string, error) {
|
||||
switch evType {
|
||||
case domain.SonarrEventTypeDownload,
|
||||
domain.SonarrEventTypeTest:
|
||||
return fmt.Sprintf("S%d.E%d %s", ep.SeasonNumber, ep.EpisodeNumber, ep.Title), nil
|
||||
default:
|
||||
return "", domain.ErrUnsupportedSonarrEventType
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/service"
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/service/internal/mock"
|
||||
)
|
||||
|
||||
func TestSonarr_Process(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, evType := range [...]domain.SonarrEventType{
|
||||
domain.SonarrEventTypeDownload,
|
||||
domain.SonarrEventTypeTest,
|
||||
} {
|
||||
t.Run("event type="+evType.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
publisher := &mock.FakePublisher{}
|
||||
publisher.PublishReturns(nil)
|
||||
|
||||
payload := generateSonarrWebhookPayload(evType)
|
||||
|
||||
err := service.NewSonaar(publisher).Process(context.Background(), payload)
|
||||
assert.NoError(t, err)
|
||||
require.Equal(t, len(payload.Episodes), publisher.PublishCallCount())
|
||||
for i, ep := range payload.Episodes {
|
||||
_, title, msg := publisher.PublishArgsForCall(i)
|
||||
assert.Equal(t, payload.Series.Title+" - New episode (Sonarr)", title)
|
||||
assert.Equal(t, fmt.Sprintf("S%d.E%d %s", ep.SeasonNumber, ep.EpisodeNumber, ep.Title), msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func generateSonarrWebhookPayload(ev domain.SonarrEventType) domain.SonarrWebhookPayload {
|
||||
s := rand.NewSource(time.Now().UnixNano())
|
||||
r := rand.New(s)
|
||||
|
||||
numEps := r.Int31n(19) + 1
|
||||
eps := make([]domain.SonarrEpisode, 0, numEps)
|
||||
for i := int32(0); i < numEps; i++ {
|
||||
eps = append(eps, domain.SonarrEpisode{
|
||||
ID: r.Int63(),
|
||||
EpisodeNumber: int16(r.Int31n(100)),
|
||||
SeasonNumber: int16(r.Int31n(100)),
|
||||
Title: uuid.NewString(),
|
||||
})
|
||||
}
|
||||
|
||||
return domain.SonarrWebhookPayload{
|
||||
EventType: ev,
|
||||
Series: domain.SonarrSeries{
|
||||
ID: r.Int63(),
|
||||
Title: uuid.NewString(),
|
||||
},
|
||||
Episodes: eps,
|
||||
}
|
||||
}
|
Reference in New Issue