refactor: add cache tests and remove cache merge task (#14)
This commit is contained in:
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -44,6 +44,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
make vendor
|
make vendor
|
||||||
|
|
||||||
|
# test
|
||||||
|
- name: tests
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
|
||||||
# git status
|
# git status
|
||||||
- run: git status
|
- run: git status
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -14,6 +14,10 @@ CGO := 0
|
|||||||
check_goreleaser:
|
check_goreleaser:
|
||||||
@command -v goreleaser >/dev/null || (echo "goreleaser is required."; exit 1)
|
@command -v goreleaser >/dev/null || (echo "goreleaser is required."; exit 1)
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: ## Run tests
|
||||||
|
go test ./... -cover -v -race ${GO_PACKAGES}
|
||||||
|
|
||||||
.PHONY: vendor
|
.PHONY: vendor
|
||||||
vendor: ## Vendor files and tidy go.mod
|
vendor: ## Vendor files and tidy go.mod
|
||||||
go mod vendor
|
go mod vendor
|
||||||
|
|||||||
48
cache/cache.go
vendored
48
cache/cache.go
vendored
@@ -1,19 +1,15 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/l3uddz/nabarr/logger"
|
"github.com/l3uddz/nabarr/logger"
|
||||||
"github.com/lefelys/state"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/xujiajun/nutsdb"
|
"github.com/xujiajun/nutsdb"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
log zerolog.Logger
|
log zerolog.Logger
|
||||||
|
|
||||||
st state.State
|
|
||||||
db *nutsdb.DB
|
db *nutsdb.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,54 +29,14 @@ func New(path string) (*Client, error) {
|
|||||||
|
|
||||||
log := logger.New("trace").With().Logger()
|
log := logger.New("trace").With().Logger()
|
||||||
|
|
||||||
// start cleaner
|
|
||||||
st, tail := state.WithShutdown()
|
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tail.End():
|
|
||||||
ticker.Stop()
|
|
||||||
tail.Done()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
// clean cache
|
|
||||||
err := db.Update(func(tx *nutsdb.Tx) error {
|
|
||||||
return db.Merge()
|
|
||||||
})
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
log.Info().Msg("Cleaned cache")
|
|
||||||
case err.Error() == "the number of files waiting to be merged is at least 2":
|
|
||||||
// there were no data files to be merged
|
|
||||||
default:
|
|
||||||
// unexpected error
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed cleaning cache")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
log: log,
|
log: log,
|
||||||
st: st,
|
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
// shutdown cleaner
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := c.st.Shutdown(ctx); err != nil {
|
|
||||||
c.log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed shutting down cache cleaner gracefully")
|
|
||||||
}
|
|
||||||
|
|
||||||
// close cache
|
// close cache
|
||||||
return c.db.Close()
|
return c.db.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
45
cache/delete_test.go
vendored
Normal file
45
cache/delete_test.go
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_Delete(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
log zerolog.Logger
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
bucket string
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "key not present",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "delete",
|
||||||
|
key: "test",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Client{
|
||||||
|
log: tt.fields.log,
|
||||||
|
db: newDb(t, "delete"),
|
||||||
|
}
|
||||||
|
if err := c.Delete(tt.args.bucket, tt.args.key); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
98
cache/get_test.go
vendored
Normal file
98
cache/get_test.go
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_Get(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
log zerolog.Logger
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
bucket string
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
sleep time.Duration
|
||||||
|
put bool
|
||||||
|
ttl time.Duration
|
||||||
|
want []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no value",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "get",
|
||||||
|
key: "test",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with value",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "get",
|
||||||
|
key: "test",
|
||||||
|
},
|
||||||
|
sleep: 1 * time.Second,
|
||||||
|
put: true,
|
||||||
|
ttl: 2 * time.Second,
|
||||||
|
want: []byte("test"),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no value post ttl",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "get",
|
||||||
|
key: "test",
|
||||||
|
},
|
||||||
|
sleep: 1 * time.Second,
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Client{
|
||||||
|
log: tt.fields.log,
|
||||||
|
db: newDb(t, "get"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.put {
|
||||||
|
if err := c.Put(tt.args.bucket, tt.args.key, tt.want, tt.ttl); (err != nil) != tt.wantErr && tt.sleep == 0 {
|
||||||
|
t.Errorf("Put() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(tt.sleep)
|
||||||
|
|
||||||
|
got, err := c.Get(tt.args.bucket, tt.args.key)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Get() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
t.Errorf("Close() error = %v, wantErr %v", err, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
98
cache/put_test.go
vendored
Normal file
98
cache/put_test.go
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_Put(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
log zerolog.Logger
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
bucket string
|
||||||
|
key string
|
||||||
|
val []byte
|
||||||
|
ttl time.Duration
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
sleep time.Duration
|
||||||
|
want []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with ttl",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "put",
|
||||||
|
key: "test",
|
||||||
|
val: []byte("testing"),
|
||||||
|
ttl: 50 * time.Millisecond,
|
||||||
|
},
|
||||||
|
want: []byte("testing"),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ttl timed out",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "put",
|
||||||
|
key: "test",
|
||||||
|
val: []byte("testing"),
|
||||||
|
ttl: 1 * time.Second,
|
||||||
|
},
|
||||||
|
sleep: 2 * time.Second,
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no ttl",
|
||||||
|
fields: fields{
|
||||||
|
log: zerolog.Logger{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
bucket: "put",
|
||||||
|
key: "test",
|
||||||
|
val: []byte("testing"),
|
||||||
|
ttl: 0,
|
||||||
|
},
|
||||||
|
want: []byte("testing"),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Client{
|
||||||
|
log: tt.fields.log,
|
||||||
|
db: newDb(t, "nabarr_put"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Put(tt.args.bucket, tt.args.key, tt.args.val, tt.args.ttl); (err != nil) != tt.wantErr && tt.sleep == 0 {
|
||||||
|
t.Errorf("Put() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(tt.sleep)
|
||||||
|
|
||||||
|
got, err := c.Get(tt.args.bucket, tt.args.key)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Put() get error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Put() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
t.Errorf("Close() error = %v, wantErr %v", err, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
24
cache/test.go
vendored
Normal file
24
cache/test.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDb(t *testing.T, dir string) *nutsdb.DB {
|
||||||
|
db, err := nutsdb.Open(nutsdb.Options{
|
||||||
|
Dir: filepath.Join(os.TempDir(), dir),
|
||||||
|
EntryIdxMode: nutsdb.HintKeyValAndRAMIdxMode,
|
||||||
|
SegmentSize: 8 * 1024 * 1024,
|
||||||
|
NodeNum: 1,
|
||||||
|
RWMode: nutsdb.FileIO,
|
||||||
|
SyncEnable: true,
|
||||||
|
StartFileLoadingMode: nutsdb.MMap,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("newDb(dir: %v) open error: %v", dir, err)
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.20.0
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
github.com/xujiajun/nutsdb v0.5.0
|
github.com/xujiajun/nutsdb v0.5.1-0.20210103130259-2812a595bc10
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go.uber.org/ratelimit v0.1.0
|
go.uber.org/ratelimit v0.1.0
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -194,8 +194,8 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
|
|||||||
github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM=
|
github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM=
|
||||||
github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc=
|
github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc=
|
||||||
github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg=
|
github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg=
|
||||||
github.com/xujiajun/nutsdb v0.5.0 h1:j/jM3Zw7Chg8WK7bAcKR0Xr7Mal47U1oJAMgySfDn9E=
|
github.com/xujiajun/nutsdb v0.5.1-0.20210103130259-2812a595bc10 h1:Pk4tD6Odq88Hzc3U5QcKEZ9nRoSTST0Rau/5z6EcYrY=
|
||||||
github.com/xujiajun/nutsdb v0.5.0/go.mod h1:owdwN0tW084RxEodABLbO7h4Z2s9WiAjZGZFhRh0/1Q=
|
github.com/xujiajun/nutsdb v0.5.1-0.20210103130259-2812a595bc10/go.mod h1:Q8FXi2zeQRluPpUl/CKQ6J7u/9gcI02J6cZp3owFLyA=
|
||||||
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0=
|
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0=
|
||||||
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k=
|
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var (
|
|||||||
|
|
||||||
func (c *Client) GetShow(tvdbId string) (*Show, error) {
|
func (c *Client) GetShow(tvdbId string) (*Show, error) {
|
||||||
// prepare request
|
// prepare request
|
||||||
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, fmt.Sprintf("/search/tvdb/%s", tvdbId)),
|
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", "tvdb", tvdbId),
|
||||||
url.Values{
|
url.Values{
|
||||||
"type": []string{"show"},
|
"type": []string{"show"},
|
||||||
"extended": []string{"full"}})
|
"extended": []string{"full"}})
|
||||||
@@ -59,7 +59,7 @@ func (c *Client) GetShow(tvdbId string) (*Show, error) {
|
|||||||
|
|
||||||
func (c *Client) GetMovie(imdbId string) (*Movie, error) {
|
func (c *Client) GetMovie(imdbId string) (*Movie, error) {
|
||||||
// prepare request
|
// prepare request
|
||||||
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, fmt.Sprintf("/search/imdb/%s", imdbId)),
|
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", "imdb", imdbId),
|
||||||
url.Values{
|
url.Values{
|
||||||
"type": []string{"movie"},
|
"type": []string{"movie"},
|
||||||
"extended": []string{"full"}})
|
"extended": []string{"full"}})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
|
|
||||||
func (c *Client) getSystemStatus() (*systemStatus, error) {
|
func (c *Client) getSystemStatus() (*systemStatus, error) {
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Get(util.JoinURL(c.apiURL, "/system/status"), rek.Headers(c.apiHeaders),
|
resp, err := rek.Get(util.JoinURL(c.apiURL, "system", "status"), rek.Headers(c.apiHeaders),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request system status: %w", err)
|
return nil, fmt.Errorf("request system status: %w", err)
|
||||||
@@ -41,7 +41,7 @@ func (c *Client) getSystemStatus() (*systemStatus, error) {
|
|||||||
|
|
||||||
func (c *Client) getQualityProfileId(profileName string) (int, error) {
|
func (c *Client) getQualityProfileId(profileName string) (int, error) {
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Get(util.JoinURL(c.apiURL, "/profile"), rek.Headers(c.apiHeaders),
|
resp, err := rek.Get(util.JoinURL(c.apiURL, "profile"), rek.Headers(c.apiHeaders),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("request quality profiles: %w", err)
|
return 0, fmt.Errorf("request quality profiles: %w", err)
|
||||||
@@ -81,7 +81,7 @@ func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare request
|
// prepare request
|
||||||
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "/movie/lookup"),
|
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "movie", "lookup"),
|
||||||
url.Values{"term": []string{fmt.Sprintf("%s:%s", mdType, mdId)}})
|
url.Values{"term": []string{fmt.Sprintf("%s:%s", mdType, mdId)}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generate movie lookup request url: %w", err)
|
return nil, fmt.Errorf("generate movie lookup request url: %w", err)
|
||||||
@@ -143,7 +143,7 @@ func (c *Client) AddMediaItem(item *media.Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Post(util.JoinURL(c.apiURL, "/movie"), rek.Headers(c.apiHeaders), rek.Json(req),
|
resp, err := rek.Post(util.JoinURL(c.apiURL, "movie"), rek.Headers(c.apiHeaders), rek.Json(req),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("request add movie: %w", err)
|
return fmt.Errorf("request add movie: %w", err)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func New(c nabarr.PvrConfig, mode string, m *media.Client, cc *cache.Client) (*C
|
|||||||
if strings.Contains(strings.ToLower(c.URL), "/api") {
|
if strings.Contains(strings.ToLower(c.URL), "/api") {
|
||||||
apiURL = c.URL
|
apiURL = c.URL
|
||||||
} else {
|
} else {
|
||||||
apiURL = util.JoinURL(c.URL, "/api")
|
apiURL = util.JoinURL(c.URL, "api")
|
||||||
}
|
}
|
||||||
|
|
||||||
// set api headers
|
// set api headers
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
|
|
||||||
func (c *Client) getSystemStatus() (*systemStatus, error) {
|
func (c *Client) getSystemStatus() (*systemStatus, error) {
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Get(util.JoinURL(c.apiURL, "/system/status"), rek.Headers(c.apiHeaders),
|
resp, err := rek.Get(util.JoinURL(c.apiURL, "system", "status"), rek.Headers(c.apiHeaders),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request system status: %w", err)
|
return nil, fmt.Errorf("request system status: %w", err)
|
||||||
@@ -41,7 +41,7 @@ func (c *Client) getSystemStatus() (*systemStatus, error) {
|
|||||||
|
|
||||||
func (c *Client) getQualityProfileId(profileName string) (int, error) {
|
func (c *Client) getQualityProfileId(profileName string) (int, error) {
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Get(util.JoinURL(c.apiURL, "/profile"), rek.Headers(c.apiHeaders),
|
resp, err := rek.Get(util.JoinURL(c.apiURL, "profile"), rek.Headers(c.apiHeaders),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("request quality profiles: %w", err)
|
return 0, fmt.Errorf("request quality profiles: %w", err)
|
||||||
@@ -71,7 +71,7 @@ func (c *Client) getQualityProfileId(profileName string) (int, error) {
|
|||||||
|
|
||||||
func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) {
|
func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) {
|
||||||
// prepare request
|
// prepare request
|
||||||
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "/series/lookup"),
|
reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "series", "lookup"),
|
||||||
url.Values{"term": []string{fmt.Sprintf("tvdb:%s", item.TvdbId)}})
|
url.Values{"term": []string{fmt.Sprintf("tvdb:%s", item.TvdbId)}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generate series lookup request url: %w", err)
|
return nil, fmt.Errorf("generate series lookup request url: %w", err)
|
||||||
@@ -133,7 +133,7 @@ func (c *Client) AddMediaItem(item *media.Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
resp, err := rek.Post(util.JoinURL(c.apiURL, "/series"), rek.Headers(c.apiHeaders), rek.Json(req),
|
resp, err := rek.Post(util.JoinURL(c.apiURL, "series"), rek.Headers(c.apiHeaders), rek.Json(req),
|
||||||
rek.Timeout(c.apiTimeout))
|
rek.Timeout(c.apiTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("request add series: %w", err)
|
return fmt.Errorf("request add series: %w", err)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func New(c nabarr.PvrConfig, mode string, m *media.Client, cc *cache.Client) (*C
|
|||||||
if strings.Contains(strings.ToLower(c.URL), "/api") {
|
if strings.Contains(strings.ToLower(c.URL), "/api") {
|
||||||
apiURL = c.URL
|
apiURL = c.URL
|
||||||
} else {
|
} else {
|
||||||
apiURL = util.JoinURL(c.URL, "/api")
|
apiURL = util.JoinURL(c.URL, "api")
|
||||||
}
|
}
|
||||||
|
|
||||||
// set api headers
|
// set api headers
|
||||||
|
|||||||
83
util/default_test.go
Normal file
83
util/default_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAtof64(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
val string
|
||||||
|
defaultVal float64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "expect whole value",
|
||||||
|
args: args{
|
||||||
|
val: "1",
|
||||||
|
defaultVal: 0,
|
||||||
|
},
|
||||||
|
want: 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expect non whole value",
|
||||||
|
args: args{
|
||||||
|
val: "1.5",
|
||||||
|
defaultVal: 0,
|
||||||
|
},
|
||||||
|
want: 1.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expect default",
|
||||||
|
args: args{
|
||||||
|
val: "invalid",
|
||||||
|
defaultVal: 0,
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Atof64(tt.args.val, tt.args.defaultVal); got != tt.want {
|
||||||
|
t.Errorf("Atof64() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtoi(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
val string
|
||||||
|
defaultVal int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "expect value",
|
||||||
|
args: args{
|
||||||
|
val: "5",
|
||||||
|
defaultVal: 0,
|
||||||
|
},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expect default",
|
||||||
|
args: args{
|
||||||
|
val: "invalid",
|
||||||
|
defaultVal: 2,
|
||||||
|
},
|
||||||
|
want: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Atoi(tt.args.val, tt.args.defaultVal); got != tt.want {
|
||||||
|
t.Errorf("Atoi() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
util/hash_test.go
Normal file
37
util/hash_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAsSHA256(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
o interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "hash struct",
|
||||||
|
args: args{
|
||||||
|
o: struct {
|
||||||
|
Name string
|
||||||
|
Surname string
|
||||||
|
Age int
|
||||||
|
}{
|
||||||
|
Name: "John",
|
||||||
|
Surname: "Smith",
|
||||||
|
Age: 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "f20fe06d96e179073fc3eebac62d7a2edf3164f0c50524d82c0c6390013bbc4a",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := AsSHA256(tt.args.o); got != tt.want {
|
||||||
|
t.Errorf("AsSHA256() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
29
util/string_test.go
Normal file
29
util/string_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestStripNonAlphaNumeric(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "remove trailing slash",
|
||||||
|
args: args{
|
||||||
|
value: "tt1234567/",
|
||||||
|
},
|
||||||
|
want: "tt1234567",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := StripNonAlphaNumeric(tt.args.value); got != tt.want {
|
||||||
|
t.Errorf("StripNonAlphaNumeric() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
97
util/url_test.go
Normal file
97
util/url_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJoinURL(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
base string
|
||||||
|
paths []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single path",
|
||||||
|
args: args{
|
||||||
|
base: "https://www.google.co.uk/",
|
||||||
|
paths: []string{"search"},
|
||||||
|
},
|
||||||
|
want: "https://www.google.co.uk/search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple path",
|
||||||
|
args: args{
|
||||||
|
base: "https://www.google.co.uk",
|
||||||
|
paths: []string{"search", "string"},
|
||||||
|
},
|
||||||
|
want: "https://www.google.co.uk/search/string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple path with slashes",
|
||||||
|
args: args{
|
||||||
|
base: "https://www.google.co.uk/",
|
||||||
|
paths: []string{"/search/", "/string/"},
|
||||||
|
},
|
||||||
|
want: "https://www.google.co.uk/search/string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := JoinURL(tt.args.base, tt.args.paths...); got != tt.want {
|
||||||
|
t.Errorf("JoinURL() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLWithQuery(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
base string
|
||||||
|
q url.Values
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "url with values",
|
||||||
|
args: args{
|
||||||
|
base: JoinURL("https://api.trakt.tv", "search", "tvdb", "12345"),
|
||||||
|
q: url.Values{
|
||||||
|
"extended": []string{"full"},
|
||||||
|
"type": []string{"show"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://api.trakt.tv/search/tvdb/12345?extended=full&type=show",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url without values",
|
||||||
|
args: args{
|
||||||
|
base: JoinURL("https://api.trakt.tv", "search", "tvdb", "12345"),
|
||||||
|
q: nil,
|
||||||
|
},
|
||||||
|
want: "https://api.trakt.tv/search/tvdb/12345",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := URLWithQuery(tt.args.base, tt.args.q)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("URLWithQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("URLWithQuery() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user