feat: add sonarr support
This commit is contained in:
parent
091b43c149
commit
08b899e620
|
@ -7,12 +7,15 @@ type ErrorCode uint8
|
|||
const (
|
||||
ErrorCodeUnknown ErrorCode = iota
|
||||
ErrorCodeValidation
|
||||
ErrorCodeIO // External I/O error such as network failure.
|
||||
)
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
switch e {
|
||||
case ErrorCodeValidation:
|
||||
return "validation-error"
|
||||
case ErrorCodeIO:
|
||||
return "io-error"
|
||||
case ErrorCodeUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
|
|
|
@ -14,6 +14,7 @@ func TestErrorCode_String(t *testing.T) {
|
|||
|
||||
assert.Equal(t, domain.ErrorCodeUnknown.String(), "internal-server-error")
|
||||
assert.Equal(t, domain.ErrorCodeValidation.String(), "validation-error")
|
||||
assert.Equal(t, domain.ErrorCodeIO.String(), "io-error")
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
|
@ -45,6 +46,16 @@ func TestError(t *testing.T) {
|
|||
expectedErr: nil,
|
||||
expectedMsg: "err err",
|
||||
},
|
||||
{
|
||||
name: "OK: WithCode(domain.ErrorCodeIO), WithMessage",
|
||||
options: []domain.ErrorOption{
|
||||
domain.WithCode(domain.ErrorCodeIO),
|
||||
domain.WithMessage("err err"),
|
||||
},
|
||||
expectedCode: domain.ErrorCodeIO,
|
||||
expectedErr: nil,
|
||||
expectedMsg: "err err",
|
||||
},
|
||||
{
|
||||
name: "OK: WithCode(domain.ErrorCodeUnknown), WithMessagef, WithErr",
|
||||
options: []domain.ErrorOption{
|
||||
|
|
|
@ -41,6 +41,8 @@ func errorCodeToHTTPStatus(code domain.ErrorCode) int {
|
|||
switch code {
|
||||
case domain.ErrorCodeValidation:
|
||||
return http.StatusBadRequest
|
||||
case domain.ErrorCodeIO:
|
||||
return http.StatusServiceUnavailable
|
||||
case domain.ErrorCodeUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
|
|
|
@ -2,7 +2,12 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
)
|
||||
|
||||
type Ntfy struct {
|
||||
|
@ -24,5 +29,33 @@ func NewNtfy(client *http.Client, url string, topic string, username string, pas
|
|||
}
|
||||
|
||||
func (n *Ntfy) Publish(ctx context.Context, title, message string) error {
|
||||
// initialize request
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, n.url+"/"+n.topic, strings.NewReader(message))
|
||||
if err != nil {
|
||||
return fmt.Errorf("http.NewRequestWithContext: %w", err)
|
||||
}
|
||||
|
||||
// set required headers
|
||||
req.Header.Set("Title", title)
|
||||
if n.username != "" && n.password != "" {
|
||||
req.SetBasicAuth(n.username, n.password)
|
||||
}
|
||||
|
||||
// send request
|
||||
resp, err := n.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client.Do: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
_, _ = io.Copy(io.Discard, resp.Body) // discard body, as it is not needed
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return domain.NewError(
|
||||
domain.WithCode(domain.ErrorCodeIO),
|
||||
domain.WithMessagef("ntfy returned unexpected status code: %d", resp.StatusCode),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/domain"
|
||||
|
||||
"gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNtfy_Publish(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
topic string
|
||||
title string
|
||||
message string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{
|
||||
name: "without authorization",
|
||||
topic: "topicc",
|
||||
title: "title",
|
||||
message: "msg",
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
{
|
||||
name: "with authorization",
|
||||
topic: "topicc",
|
||||
title: "title",
|
||||
message: "msg",
|
||||
username: "uname",
|
||||
password: "pwd",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path[1:] != tt.topic {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Header.Get("Title") != tt.title {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("invalid title"))
|
||||
return
|
||||
}
|
||||
|
||||
if uname, pwd, _ := r.BasicAuth(); uname != tt.username || pwd != tt.password {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("invalid username or password"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
err := service.NewNtfy(srv.Client(), srv.URL, tt.topic, tt.username, tt.password).
|
||||
Publish(context.Background(), tt.title, tt.message)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ERR: status code != 200", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
err := service.NewNtfy(srv.Client(), srv.URL, "topic", "", "").
|
||||
Publish(context.Background(), "title", "msg")
|
||||
assert.ErrorIs(t, err, domain.NewError(
|
||||
domain.WithCode(domain.ErrorCodeIO),
|
||||
domain.WithMessagef("ntfy returned unexpected status code: %d", http.StatusNotImplemented),
|
||||
))
|
||||
})
|
||||
}
|
Reference in New Issue