diff --git a/sonarr/importlist.go b/sonarr/importlist.go new file mode 100644 index 0000000..353f019 --- /dev/null +++ b/sonarr/importlist.go @@ -0,0 +1,144 @@ +package sonarr + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "path" + + "golift.io/starr" +) + +const bpImportList = APIver + "/importList" + +// ImportListInput is the input for a new or updated import list. +type ImportListInput struct { + EnableAutomaticAdd bool `json:"enableAutomaticAdd"` + SeasonFolder bool `json:"seasonFolder"` + LanguageProfileID int64 `json:"languageProfileId"` + QualityProfileID int64 `json:"qualityProfileId"` + ID int64 `json:"id,omitempty"` + ShouldMonitor string `json:"shouldMonitor"` + RootFolderPath string `json:"rootFolderPath"` + SeriesType string `json:"seriesType"` + ConfigContract string `json:"configContract"` + Implementation string `json:"implementation"` + Name string `json:"name"` + Tags []int `json:"tags"` + Fields []*starr.FieldInput `json:"fields"` +} + +// ImportListOutput is the output from the import list methods. +type ImportListOutput struct { + EnableAutomaticAdd bool `json:"enableAutomaticAdd"` + SeasonFolder bool `json:"seasonFolder"` + LanguageProfileID int64 `json:"languageProfileId"` + QualityProfileID int64 `json:"qualityProfileId"` + ListOrder int64 `json:"listOrder"` + ID int64 `json:"id"` + ShouldMonitor string `json:"shouldMonitor"` + RootFolderPath string `json:"rootFolderPath"` + SeriesType string `json:"seriesType"` + ListType string `json:"listType"` + Name string `json:"name"` + ImplementationName string `json:"implementationName"` + Implementation string `json:"implementation"` + ConfigContract string `json:"configContract"` + InfoLink string `json:"infoLink"` + Tags []int `json:"tags"` + Fields []*starr.FieldOutput `json:"fields"` +} + +// GetImportLists returns all configured import lists. +func (s *Sonarr) GetImportLists() ([]*ImportListOutput, error) { + return s.GetImportListsContext(context.Background()) +} + +// GetImportListsContext returns all configured import lists. +func (s *Sonarr) GetImportListsContext(ctx context.Context) ([]*ImportListOutput, error) { + var output []*ImportListOutput + + req := starr.Request{URI: bpImportList} + if err := s.GetInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", &req, err) + } + + return output, nil +} + +// GetImportList returns a single import list. +func (s *Sonarr) GetImportList(importListID int64) (*ImportListOutput, error) { + return s.GetImportListContext(context.Background(), importListID) +} + +// GetIndGetImportListContextexer returns a single import list. +func (s *Sonarr) GetImportListContext(ctx context.Context, importListID int64) (*ImportListOutput, error) { + var output ImportListOutput + + req := starr.Request{URI: path.Join(bpImportList, fmt.Sprint(importListID))} + if err := s.GetInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Get(%s): %w", &req, err) + } + + return &output, nil +} + +// AddImportList creates a import list. +func (s *Sonarr) AddImportList(importList *ImportListInput) (*ImportListOutput, error) { + return s.AddImportListContext(context.Background(), importList) +} + +// AddImportListContext creates a import list. +func (s *Sonarr) AddImportListContext(ctx context.Context, importList *ImportListInput) (*ImportListOutput, error) { + var output ImportListOutput + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(importList); err != nil { + return nil, fmt.Errorf("json.Marshal(%s): %w", bpImportList, err) + } + + req := starr.Request{URI: bpImportList, Body: &body} + if err := s.PostInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Post(%s): %w", &req, err) + } + + return &output, nil +} + +// UpdateImportList updates the import list. +func (s *Sonarr) UpdateImportList(importList *ImportListInput) (*ImportListOutput, error) { + return s.UpdateImportListContext(context.Background(), importList) +} + +// UpdateImportListContext updates the import list. +func (s *Sonarr) UpdateImportListContext(ctx context.Context, importList *ImportListInput) (*ImportListOutput, error) { + var output ImportListOutput + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(importList); err != nil { + return nil, fmt.Errorf("json.Marshal(%s): %w", bpImportList, err) + } + + req := starr.Request{URI: path.Join(bpImportList, fmt.Sprint(importList.ID)), Body: &body} + if err := s.PutInto(ctx, req, &output); err != nil { + return nil, fmt.Errorf("api.Put(%s): %w", &req, err) + } + + return &output, nil +} + +// DeleteImportList removes a single import list. +func (s *Sonarr) DeleteImportList(importListID int64) error { + return s.DeleteImportListContext(context.Background(), importListID) +} + +// DeleteImportListContext removes a single import list. +func (s *Sonarr) DeleteImportListContext(ctx context.Context, importListID int64) error { + req := starr.Request{URI: path.Join(bpImportList, fmt.Sprint(importListID))} + if err := s.DeleteAny(ctx, req); err != nil { + return fmt.Errorf("api.Delete(%s): %w", &req, err) + } + + return nil +} diff --git a/sonarr/importlist_test.go b/sonarr/importlist_test.go new file mode 100644 index 0000000..ca40615 --- /dev/null +++ b/sonarr/importlist_test.go @@ -0,0 +1,474 @@ +package sonarr_test + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "golift.io/starr" + "golift.io/starr/sonarr" +) + +const importListResponseBody = `{ + "enableAutomaticAdd": false, + "shouldMonitor": "all", + "rootFolderPath": "/config", + "qualityProfileId": 1, + "languageProfileId": 1, + "seriesType": "standard", + "seasonFolder": true, + "listType": "plex", + "listOrder": 1, + "name": "PlexImport", + "fields": [ + { + "order": 0, + "name": "accessToken", + "label": "Access Token", + "type": "textbox", + "value": "test", + "advanced": false, + "hidden": "hidden" + }, + { + "order": 1, + "name": "signIn", + "label": "Authenticate with Plex.tv", + "value": "startOAuth", + "type": "oAuth", + "advanced": false + } + ], + "implementationName": "Plex Watchlist", + "implementation": "PlexImport", + "configContract": "PlexListSettings", + "infoLink": "https://wiki.servarr.com/sonarr/supported#pleximport", + "tags": [], + "id": 4 + }` + +const addImportList = `{"enableAutomaticAdd":false,"seasonFolder":true,"languageProfileId":1,` + + `"qualityProfileId":1,"shouldMonitor":"all","rootFolderPath":"/config","seriesType":"standard",` + + `"configContract":"PlexListSettings","implementation":"PlexImport","name":"PlexImport",` + + `"tags":[],"fields":[{"name":"accessToken","value":"test"}]}` + +const updateImportList = `{"enableAutomaticAdd":false,"seasonFolder":true,"languageProfileId":1,` + + `"qualityProfileId":1,"id":4,"shouldMonitor":"all","rootFolderPath":"/config","seriesType":"standard",` + + `"configContract":"PlexListSettings","implementation":"PlexImport","name":"PlexImport",` + + `"tags":[],"fields":[{"name":"accessToken","value":"test"}]}` + +func TestGetImportLists(t *testing.T) { + t.Parallel() + + tests := []*starr.TestMockData{ + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList"), + ExpectedRequest: "", + ExpectedMethod: "GET", + ResponseStatus: 200, + ResponseBody: "[" + importListResponseBody + "]", + WithRequest: nil, + WithResponse: []*sonarr.ImportListOutput{ + { + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + ListType: "plex", + ListOrder: 1, + Name: "PlexImport", + Fields: []*starr.FieldOutput{ + { + Order: 0, + Name: "accessToken", + Label: "Access Token", + Type: "textbox", + Value: "test", + Advanced: false, + Hidden: "hidden", + }, + { + Order: 1, + Name: "signIn", + Label: "Authenticate with Plex.tv", + Value: "startOAuth", + Type: "oAuth", + Advanced: false, + }, + }, + ImplementationName: "Plex Watchlist", + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + InfoLink: "https://wiki.servarr.com/sonarr/supported#pleximport", + Tags: []int{}, + ID: 4, + }, + }, + WithError: nil, + }, + { + Name: "404", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList"), + ExpectedMethod: "GET", + ResponseStatus: 404, + ResponseBody: `{"message": "NotFound"}`, + WithError: starr.ErrInvalidStatusCode, + WithResponse: ([]*sonarr.ImportListOutput)(nil), + }, + } + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + mockServer := test.GetMockServer(t) + client := sonarr.New(starr.New("mockAPIkey", mockServer.URL, 0)) + output, err := client.GetImportLists() + assert.ErrorIs(t, err, test.WithError, "error is not the same as expected") + assert.EqualValues(t, test.WithResponse, output, "response is not the same as expected") + }) + } +} + +func TestGetImportList(t *testing.T) { + t.Parallel() + + tests := []*starr.TestMockData{ + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "1"), + ExpectedRequest: "", + ExpectedMethod: "GET", + ResponseStatus: 200, + ResponseBody: importListResponseBody, + WithRequest: nil, + WithResponse: &sonarr.ImportListOutput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + ListType: "plex", + ListOrder: 1, + Name: "PlexImport", + Fields: []*starr.FieldOutput{ + { + Order: 0, + Name: "accessToken", + Label: "Access Token", + Type: "textbox", + Value: "test", + Advanced: false, + Hidden: "hidden", + }, + { + Order: 1, + Name: "signIn", + Label: "Authenticate with Plex.tv", + Value: "startOAuth", + Type: "oAuth", + Advanced: false, + }, + }, + ImplementationName: "Plex Watchlist", + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + InfoLink: "https://wiki.servarr.com/sonarr/supported#pleximport", + Tags: []int{}, + ID: 4, + }, + WithError: nil, + }, + { + Name: "404", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "1"), + ExpectedMethod: "GET", + ResponseStatus: 404, + ResponseBody: `{"message": "NotFound"}`, + WithError: starr.ErrInvalidStatusCode, + WithResponse: (*sonarr.ImportListOutput)(nil), + }, + } + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + mockServer := test.GetMockServer(t) + client := sonarr.New(starr.New("mockAPIkey", mockServer.URL, 0)) + output, err := client.GetImportList(1) + assert.ErrorIs(t, err, test.WithError, "error is not the same as expected") + assert.EqualValues(t, test.WithResponse, output, "response is not the same as expected") + }) + } +} + +func TestAddImportList(t *testing.T) { + t.Parallel() + + tests := []*starr.TestMockData{ + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList"), + ExpectedMethod: "POST", + ResponseStatus: 200, + WithRequest: &sonarr.ImportListInput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + Name: "PlexImport", + Fields: []*starr.FieldInput{ + { + Name: "accessToken", + Value: "test", + }, + }, + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + Tags: []int{}, + }, + ExpectedRequest: addImportList + "\n", + ResponseBody: importListResponseBody, + WithResponse: &sonarr.ImportListOutput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + ListType: "plex", + ListOrder: 1, + Name: "PlexImport", + Fields: []*starr.FieldOutput{ + { + Order: 0, + Name: "accessToken", + Label: "Access Token", + Type: "textbox", + Value: "test", + Advanced: false, + Hidden: "hidden", + }, + { + Order: 1, + Name: "signIn", + Label: "Authenticate with Plex.tv", + Value: "startOAuth", + Type: "oAuth", + Advanced: false, + }, + }, + ImplementationName: "Plex Watchlist", + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + InfoLink: "https://wiki.servarr.com/sonarr/supported#pleximport", + Tags: []int{}, + ID: 4, + }, + WithError: nil, + }, + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList"), + ExpectedMethod: "POST", + ResponseStatus: 404, + WithRequest: &sonarr.ImportListInput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + Name: "PlexImport", + Fields: []*starr.FieldInput{ + { + Name: "accessToken", + Value: "test", + }, + }, + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + Tags: []int{}, + }, + ExpectedRequest: addImportList + "\n", + ResponseBody: `{"message": "NotFound"}`, + WithError: starr.ErrInvalidStatusCode, + WithResponse: (*sonarr.ImportListOutput)(nil), + }, + } + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + mockServer := test.GetMockServer(t) + client := sonarr.New(starr.New("mockAPIkey", mockServer.URL, 0)) + output, err := client.AddImportList(test.WithRequest.(*sonarr.ImportListInput)) + assert.ErrorIs(t, err, test.WithError, "error is not the same as expected") + assert.EqualValues(t, test.WithResponse, output, "response is not the same as expected") + }) + } +} + +func TestUpdateImportList(t *testing.T) { + t.Parallel() + + tests := []*starr.TestMockData{ + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "4"), + ExpectedMethod: "PUT", + ResponseStatus: 200, + WithRequest: &sonarr.ImportListInput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + Name: "PlexImport", + Fields: []*starr.FieldInput{ + { + Name: "accessToken", + Value: "test", + }, + }, + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + Tags: []int{}, + ID: 4, + }, + ExpectedRequest: updateImportList + "\n", + ResponseBody: importListResponseBody, + WithResponse: &sonarr.ImportListOutput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + ListType: "plex", + ListOrder: 1, + Name: "PlexImport", + Fields: []*starr.FieldOutput{ + { + Order: 0, + Name: "accessToken", + Label: "Access Token", + Type: "textbox", + Value: "test", + Advanced: false, + Hidden: "hidden", + }, + { + Order: 1, + Name: "signIn", + Label: "Authenticate with Plex.tv", + Value: "startOAuth", + Type: "oAuth", + Advanced: false, + }, + }, + ImplementationName: "Plex Watchlist", + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + InfoLink: "https://wiki.servarr.com/sonarr/supported#pleximport", + Tags: []int{}, + ID: 4, + }, + WithError: nil, + }, + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "4"), + ExpectedMethod: "PUT", + ResponseStatus: 404, + WithRequest: &sonarr.ImportListInput{ + EnableAutomaticAdd: false, + ShouldMonitor: "all", + RootFolderPath: "/config", + QualityProfileID: 1, + LanguageProfileID: 1, + SeriesType: "standard", + SeasonFolder: true, + Name: "PlexImport", + Fields: []*starr.FieldInput{ + { + Name: "accessToken", + Value: "test", + }, + }, + Implementation: "PlexImport", + ConfigContract: "PlexListSettings", + Tags: []int{}, + ID: 4, + }, + ExpectedRequest: updateImportList + "\n", + ResponseBody: `{"message": "NotFound"}`, + WithError: starr.ErrInvalidStatusCode, + WithResponse: (*sonarr.ImportListOutput)(nil), + }, + } + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + mockServer := test.GetMockServer(t) + client := sonarr.New(starr.New("mockAPIkey", mockServer.URL, 0)) + output, err := client.UpdateImportList(test.WithRequest.(*sonarr.ImportListInput)) + assert.ErrorIs(t, err, test.WithError, "error is not the same as expected") + assert.EqualValues(t, test.WithResponse, output, "response is not the same as expected") + }) + } +} + +func TestDeleteImportList(t *testing.T) { + t.Parallel() + + tests := []*starr.TestMockData{ + { + Name: "200", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "2"), + ExpectedMethod: "DELETE", + WithRequest: int64(2), + ResponseStatus: 200, + ResponseBody: "{}", + WithError: nil, + }, + { + Name: "404", + ExpectedPath: path.Join("/", starr.API, sonarr.APIver, "importList", "2"), + ExpectedMethod: "DELETE", + WithRequest: int64(2), + ResponseStatus: 404, + ResponseBody: `{"message": "NotFound"}`, + WithError: starr.ErrInvalidStatusCode, + }, + } + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + mockServer := test.GetMockServer(t) + client := sonarr.New(starr.New("mockAPIkey", mockServer.URL, 0)) + err := client.DeleteImportList(test.WithRequest.(int64)) + assert.ErrorIs(t, err, test.WithError, "error is not the same as expected") + }) + } +}