feat: add sonarr support

This commit is contained in:
Dawid Wysokiński 2022-07-17 09:08:23 +02:00
parent 08b899e620
commit e9f35e9bf4
Signed by: Kichiyaki
GPG Key ID: 1ECC5DE481BE5184
7 changed files with 269 additions and 3 deletions

1
go.mod
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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