290 lines
7.1 KiB
Go
290 lines
7.1 KiB
Go
package ntfy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"coda/internal/config"
|
|
"coda/internal/models"
|
|
)
|
|
|
|
type mockStore struct {
|
|
data map[string]string
|
|
}
|
|
|
|
func (m *mockStore) GetAll(_ context.Context) (map[string]string, error) {
|
|
result := make(map[string]string, len(m.data))
|
|
for k, v := range m.data {
|
|
result[k] = v
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockStore) Set(_ context.Context, key, value string) error {
|
|
if m.data == nil {
|
|
m.data = map[string]string{}
|
|
}
|
|
m.data[key] = value
|
|
return nil
|
|
}
|
|
|
|
func (m *mockStore) Delete(_ context.Context, key string) error {
|
|
delete(m.data, key)
|
|
return nil
|
|
}
|
|
|
|
func newClientWithOverrides(t *testing.T, overrides map[string]string) *Client {
|
|
t.Helper()
|
|
svc, err := config.NewService(context.Background(), &mockStore{data: overrides})
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
return New(svc)
|
|
}
|
|
|
|
func readPayload(t *testing.T, r *http.Request) payload {
|
|
t.Helper()
|
|
b, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("read body: %v", err)
|
|
}
|
|
var p payload
|
|
if err := json.Unmarshal(b, &p); err != nil {
|
|
t.Fatalf("unmarshal payload: %v (body: %s)", err, string(b))
|
|
}
|
|
return p
|
|
}
|
|
|
|
func TestNotifySendsDoneMessage(t *testing.T) {
|
|
var (
|
|
gotMethod string
|
|
gotPath string
|
|
gotContentType string
|
|
gotPayload payload
|
|
wasCalled atomic.Bool
|
|
)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
wasCalled.Store(true)
|
|
gotMethod = r.Method
|
|
gotPath = r.URL.Path
|
|
gotContentType = r.Header.Get("Content-Type")
|
|
gotPayload = readPayload(t, r)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "false",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
err := c.Notify(context.Background(), &models.Job{
|
|
ID: "job-1",
|
|
Artist: "Artist",
|
|
Album: "Album",
|
|
Status: models.StatusDone,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
|
|
if !wasCalled.Load() {
|
|
t.Fatal("expected HTTP request to be made")
|
|
}
|
|
if gotMethod != http.MethodPost {
|
|
t.Errorf("method = %q, want POST", gotMethod)
|
|
}
|
|
if gotPath != "/" {
|
|
t.Errorf("path = %q, want /", gotPath)
|
|
}
|
|
if gotContentType != "application/json" {
|
|
t.Errorf("Content-Type = %q, want application/json", gotContentType)
|
|
}
|
|
if gotPayload.Topic != "coda" {
|
|
t.Errorf("topic = %q, want coda", gotPayload.Topic)
|
|
}
|
|
if gotPayload.Title != "Coda: Artist - Album completed" {
|
|
t.Errorf("title = %q, want completed title", gotPayload.Title)
|
|
}
|
|
if gotPayload.Message != "Conversion completed successfully" {
|
|
t.Errorf("message = %q, want success message", gotPayload.Message)
|
|
}
|
|
if gotPayload.Priority != 3 {
|
|
t.Errorf("priority = %d, want 3", gotPayload.Priority)
|
|
}
|
|
if len(gotPayload.Tags) != 1 || gotPayload.Tags[0] != "white_check_mark" {
|
|
t.Errorf("tags = %v, want [white_check_mark]", gotPayload.Tags)
|
|
}
|
|
}
|
|
|
|
func TestNotifySendsErrorMessage(t *testing.T) {
|
|
var gotPayload payload
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotPayload = readPayload(t, r)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "false",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
err := c.Notify(context.Background(), &models.Job{
|
|
ID: "job-1",
|
|
Artist: "Artist",
|
|
Album: "Album",
|
|
Status: models.StatusError,
|
|
ErrorMsg: "validation failed",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
|
|
if gotPayload.Title != "Coda: Artist - Album failed" {
|
|
t.Errorf("title = %q, want failed title", gotPayload.Title)
|
|
}
|
|
if gotPayload.Message != "validation failed" {
|
|
t.Errorf("message = %q, want error message", gotPayload.Message)
|
|
}
|
|
if gotPayload.Priority != 4 {
|
|
t.Errorf("priority = %d, want 4", gotPayload.Priority)
|
|
}
|
|
if len(gotPayload.Tags) != 1 || gotPayload.Tags[0] != "x" {
|
|
t.Errorf("tags = %v, want [x]", gotPayload.Tags)
|
|
}
|
|
}
|
|
|
|
func TestNotifyErrorMsgWithJSONContent(t *testing.T) {
|
|
var gotPayload payload
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotPayload = readPayload(t, r)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "false",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
jsonErr := `{"error": "ffmpeg exit code 1", "details": "no such file"}`
|
|
err := c.Notify(context.Background(), &models.Job{
|
|
ID: "job-1",
|
|
Artist: "Artist",
|
|
Album: "Album",
|
|
Status: models.StatusError,
|
|
ErrorMsg: jsonErr,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
|
|
if gotPayload.Message != jsonErr {
|
|
t.Errorf("message = %q, want raw JSON string as message", gotPayload.Message)
|
|
}
|
|
}
|
|
|
|
func TestNotifyDryRunSkipsRequest(t *testing.T) {
|
|
var wasCalled atomic.Bool
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
wasCalled.Store(true)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "true",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
if err := c.Notify(context.Background(), &models.Job{ID: "job-1"}); err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
if wasCalled.Load() {
|
|
t.Error("no HTTP request should be made in dry run")
|
|
}
|
|
}
|
|
|
|
func TestNotifyDisabledSkipsRequest(t *testing.T) {
|
|
var wasCalled atomic.Bool
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
wasCalled.Store(true)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"NTFY_ENABLED": "false",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
if err := c.Notify(context.Background(), &models.Job{ID: "job-1"}); err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
if wasCalled.Load() {
|
|
t.Error("no HTTP request should be made when ntfy is disabled")
|
|
}
|
|
}
|
|
|
|
func TestNotifySendsBearerToken(t *testing.T) {
|
|
var gotAuth string
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth = r.Header.Get("Authorization")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "false",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": srv.URL,
|
|
"NTFY_TOPIC": "coda",
|
|
"NTFY_TOKEN": "secret",
|
|
})
|
|
|
|
if err := c.Notify(context.Background(), &models.Job{ID: "job-1", Status: models.StatusDone}); err != nil {
|
|
t.Fatalf("Notify: %v", err)
|
|
}
|
|
if gotAuth != "Bearer secret" {
|
|
t.Errorf("authorization = %q, want Bearer secret", gotAuth)
|
|
}
|
|
}
|
|
|
|
func TestNotifyNetworkErrorReturnsError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
url := srv.URL
|
|
srv.Close()
|
|
|
|
c := newClientWithOverrides(t, map[string]string{
|
|
"DRY_RUN": "false",
|
|
"NTFY_ENABLED": "true",
|
|
"NTFY_URL": url,
|
|
"NTFY_TOPIC": "coda",
|
|
})
|
|
|
|
if err := c.Notify(context.Background(), &models.Job{ID: "job-1"}); err == nil {
|
|
t.Fatal("expected network error")
|
|
}
|
|
}
|