feat(ci): add a bunch more linters
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// Package ctrladmin provides HTTP handlers for admin UI
|
||||
package ctrladmin
|
||||
|
||||
import (
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"go.senan.xyz/gonic/db"
|
||||
"go.senan.xyz/gonic/playlist"
|
||||
@@ -94,15 +95,20 @@ func (c *Controller) WithLogging(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func (c *Controller) WithCORS(next http.Handler) http.Handler {
|
||||
allowMethods := strings.Join(
|
||||
[]string{http.MethodPost, http.MethodGet, http.MethodOptions, http.MethodPut, http.MethodDelete},
|
||||
", ",
|
||||
)
|
||||
allowHeaders := strings.Join(
|
||||
[]string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"},
|
||||
", ",
|
||||
)
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods",
|
||||
"POST, GET, OPTIONS, PUT, DELETE",
|
||||
)
|
||||
w.Header().Set("Access-Control-Allow-Headers",
|
||||
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization",
|
||||
)
|
||||
if r.Method == "OPTIONS" {
|
||||
w.Header().Set("Access-Control-Allow-Methods", allowMethods)
|
||||
w.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
||||
if r.Method == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
)
|
||||
|
||||
func TestInfoCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := mockfs.New(t)
|
||||
m.AddItems()
|
||||
m.ScanAndClean()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package ctrlsubsonic provides HTTP handlers for subsonic API
|
||||
package ctrlsubsonic
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:thelper
|
||||
package ctrlsubsonic
|
||||
|
||||
import (
|
||||
@@ -80,7 +81,10 @@ func makeHTTPMockWithAdmin(query url.Values) (*httptest.ResponseRecorder, *http.
|
||||
func runQueryCases(t *testing.T, contr *Controller, h handlerSubsonic, cases []*queryCase) {
|
||||
t.Helper()
|
||||
for _, qc := range cases {
|
||||
qc := qc
|
||||
t.Run(qc.expectPath, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rr, req := makeHTTPMock(qc.params)
|
||||
contr.H(h).ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
@@ -91,7 +95,7 @@ func runQueryCases(t *testing.T, contr *Controller, h handlerSubsonic, cases []*
|
||||
goldenPath := makeGoldenPath(t.Name())
|
||||
goldenRegen := os.Getenv("GONIC_REGEN")
|
||||
if goldenRegen == "*" || (goldenRegen != "" && strings.HasPrefix(t.Name(), goldenRegen)) {
|
||||
_ = os.WriteFile(goldenPath, []byte(body), 0600)
|
||||
_ = os.WriteFile(goldenPath, []byte(body), 0o600)
|
||||
t.Logf("golden file %q regenerated for %s", goldenPath, t.Name())
|
||||
t.SkipNow()
|
||||
}
|
||||
@@ -120,14 +124,13 @@ func runQueryCases(t *testing.T, contr *Controller, h handlerSubsonic, cases []*
|
||||
}
|
||||
}
|
||||
|
||||
func makeController(t *testing.T) *Controller { return makec(t, []string{""}, false) }
|
||||
func makeControllerRoots(t *testing.T, r []string) *Controller { return makec(t, r, false) }
|
||||
func makeControllerAudio(t *testing.T) *Controller { return makec(t, []string{""}, true) }
|
||||
func makeController(tb testing.TB) *Controller { return makec(tb, []string{""}, false) }
|
||||
func makeControllerRoots(tb testing.TB, r []string) *Controller { return makec(tb, r, false) }
|
||||
|
||||
func makec(t *testing.T, roots []string, audio bool) *Controller {
|
||||
t.Helper()
|
||||
func makec(tb testing.TB, roots []string, audio bool) *Controller {
|
||||
tb.Helper()
|
||||
|
||||
m := mockfs.NewWithDirs(t, roots)
|
||||
m := mockfs.NewWithDirs(tb, roots)
|
||||
for _, root := range roots {
|
||||
m.AddItemsPrefixWithCovers(root)
|
||||
if !audio {
|
||||
|
||||
@@ -115,7 +115,7 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response {
|
||||
}
|
||||
|
||||
// ServeGetAlbumList handles the getAlbumList view.
|
||||
// changes to this function should be reflected in in _by_tags.go's
|
||||
// changes to this function should be reflected in _by_tags.go's
|
||||
// getAlbumListTwo() function
|
||||
func (c *Controller) ServeGetAlbumList(r *http.Request) *spec.Response {
|
||||
params := r.Context().Value(CtxParams).(params.Params)
|
||||
@@ -130,8 +130,7 @@ func (c *Controller) ServeGetAlbumList(r *http.Request) *spec.Response {
|
||||
case "alphabeticalByName":
|
||||
q = q.Order("right_path")
|
||||
case "byYear":
|
||||
y1, y2 :=
|
||||
params.GetOrInt("fromYear", 1800),
|
||||
y1, y2 := params.GetOrInt("fromYear", 1800),
|
||||
params.GetOrInt("toYear", 2200)
|
||||
// support some clients sending wrong order like DSub
|
||||
q = q.Where("tag_year BETWEEN ? AND ?", min(y1, y2), max(y1, y2))
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestGetIndexes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
contr := makeControllerRoots(t, []string{"m-0", "m-1"})
|
||||
|
||||
runQueryCases(t, contr, contr.ServeGetIndexes, []*queryCase{
|
||||
@@ -18,6 +20,8 @@ func TestGetIndexes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetMusicDirectory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
contr := makeController(t)
|
||||
|
||||
runQueryCases(t, contr, contr.ServeGetMusicDirectory, []*queryCase{
|
||||
|
||||
@@ -130,7 +130,7 @@ func (c *Controller) ServeGetAlbum(r *http.Request) *spec.Response {
|
||||
}
|
||||
|
||||
// ServeGetAlbumListTwo handles the getAlbumList2 view.
|
||||
// changes to this function should be reflected in in _by_folder.go's
|
||||
// changes to this function should be reflected in _by_folder.go's
|
||||
// getAlbumList() function
|
||||
func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response {
|
||||
params := r.Context().Value(CtxParams).(params.Params)
|
||||
@@ -147,8 +147,7 @@ func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response {
|
||||
case "alphabeticalByName":
|
||||
q = q.Order("tag_title")
|
||||
case "byYear":
|
||||
y1, y2 :=
|
||||
params.GetOrInt("fromYear", 1800),
|
||||
y1, y2 := params.GetOrInt("fromYear", 1800),
|
||||
params.GetOrInt("toYear", 2200)
|
||||
// support some clients sending wrong order like DSub
|
||||
q = q.Where("tag_year BETWEEN ? AND ?", min(y1, y2), max(y1, y2))
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//nolint:tparallel,paralleltest,thelper
|
||||
package ctrlsubsonic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -37,8 +39,10 @@ const (
|
||||
newstation1HomepageURL = "https://www.kcrw.com/music/shows/eclectic24"
|
||||
)
|
||||
|
||||
const newstation2StreamURL = "http://media.kcrw.com/pls/kcrwsantabarbara.pls"
|
||||
const newstation2Name = "KCRW Santa Barbara"
|
||||
const (
|
||||
newstation2StreamURL = "http://media.kcrw.com/pls/kcrwsantabarbara.pls"
|
||||
newstation2Name = "KCRW Santa Barbara"
|
||||
)
|
||||
|
||||
const station3ID = "ir-3"
|
||||
|
||||
@@ -48,16 +52,18 @@ func TestInternetRadio(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
contr := makeController(t)
|
||||
t.Run("TestInternetRadioInitialEmpty", func(t *testing.T) { testInternetRadioInitialEmpty(t, contr) })
|
||||
t.Run("TestInternetRadioBadCreates", func(t *testing.T) { testInternetRadioBadCreates(t, contr) })
|
||||
t.Run("TestInternetRadioInitialAdds", func(t *testing.T) { testInternetRadioInitialAdds(t, contr) })
|
||||
t.Run("TestInternetRadioUpdateHomepage", func(t *testing.T) { testInternetRadioUpdateHomepage(t, contr) })
|
||||
t.Run("TestInternetRadioNotAdmin", func(t *testing.T) { testInternetRadioNotAdmin(t, contr) })
|
||||
t.Run("TestInternetRadioUpdates", func(t *testing.T) { testInternetRadioUpdates(t, contr) })
|
||||
t.Run("TestInternetRadioDeletes", func(t *testing.T) { testInternetRadioDeletes(t, contr) })
|
||||
t.Run("initial empty", func(t *testing.T) { testInternetRadioInitialEmpty(t, contr) })
|
||||
t.Run("bad creates", func(t *testing.T) { testInternetRadioBadCreates(t, contr) })
|
||||
t.Run("initial adds", func(t *testing.T) { testInternetRadioInitialAdds(t, contr) })
|
||||
t.Run("update home page", func(t *testing.T) { testInternetRadioUpdateHomepage(t, contr) })
|
||||
t.Run("not admin", func(t *testing.T) { testInternetRadioNotAdmin(t, contr) })
|
||||
t.Run("updates", func(t *testing.T) { testInternetRadioUpdates(t, contr) })
|
||||
t.Run("deletes", func(t *testing.T) { testInternetRadioDeletes(t, contr) })
|
||||
}
|
||||
|
||||
func runTestCase(t *testing.T, contr *Controller, h handlerSubsonic, q url.Values, admin bool) *spec.SubsonicResponse {
|
||||
t.Helper()
|
||||
|
||||
var rr *httptest.ResponseRecorder
|
||||
var req *http.Request
|
||||
|
||||
@@ -74,18 +80,17 @@ func runTestCase(t *testing.T, contr *Controller, h handlerSubsonic, q url.Value
|
||||
|
||||
var response spec.SubsonicResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
|
||||
switch ty := err.(type) {
|
||||
case *json.SyntaxError:
|
||||
jsn := body[0:ty.Offset]
|
||||
jsn += "<--(Invalid Character)"
|
||||
t.Fatalf("invalid character at offset %v\n %s", ty.Offset, jsn)
|
||||
case *json.UnmarshalTypeError:
|
||||
jsn := body[0:ty.Offset]
|
||||
jsn += "<--(Invalid Type)"
|
||||
t.Fatalf("invalid type at offset %v\n %s", ty.Offset, jsn)
|
||||
default:
|
||||
t.Fatalf("json unmarshal failed: %s", err.Error())
|
||||
var jsonSyntaxError *json.SyntaxError
|
||||
if errors.As(err, &jsonSyntaxError) {
|
||||
t.Fatalf("invalid character at offset %v\n %s <--", jsonSyntaxError.Offset, body[0:jsonSyntaxError.Offset])
|
||||
}
|
||||
|
||||
var jsonUnmarshalTypeError *json.UnmarshalTypeError
|
||||
if errors.As(err, &jsonSyntaxError) {
|
||||
t.Fatalf("invalid type at offset %v\n %s <--", jsonUnmarshalTypeError.Offset, body[0:jsonUnmarshalTypeError.Offset])
|
||||
}
|
||||
|
||||
t.Fatalf("json unmarshal failed: %v", err)
|
||||
}
|
||||
|
||||
return &response
|
||||
|
||||
@@ -180,6 +180,7 @@ func (c *Controller) ServeDeletePlaylist(r *http.Request) *spec.Response {
|
||||
func playlistIDEncode(path string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(path))
|
||||
}
|
||||
|
||||
func playlistIDDecode(id string) string {
|
||||
path, _ := base64.URLEncoding.DecodeString(id)
|
||||
return string(path)
|
||||
|
||||
@@ -62,8 +62,6 @@ func streamGetTranscodeMeta(dbc *db.DB, userID int, client string) spec.Transcod
|
||||
}
|
||||
}
|
||||
|
||||
var errUnknownMediaType = fmt.Errorf("media type is unknown")
|
||||
|
||||
func streamUpdateStats(dbc *db.DB, userID int, track *db.Track, playTime time.Time) error {
|
||||
var play db.Play
|
||||
err := dbc.
|
||||
|
||||
@@ -35,9 +35,7 @@ import (
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoValues = errors.New("no values provided")
|
||||
)
|
||||
var ErrNoValues = errors.New("no values provided")
|
||||
|
||||
// some thin wrappers
|
||||
// may be needed when cleaning up parse() below
|
||||
@@ -62,7 +60,6 @@ func parse(values []string, i interface{}) error {
|
||||
}
|
||||
var err error
|
||||
switch v := i.(type) {
|
||||
|
||||
// *T
|
||||
case *string:
|
||||
*v, err = parseStr(values[0])
|
||||
|
||||
@@ -30,18 +30,17 @@ const (
|
||||
separator = "-"
|
||||
)
|
||||
|
||||
//nolint:musttag
|
||||
type ID struct {
|
||||
Type IDT
|
||||
Value int
|
||||
}
|
||||
|
||||
func New(in string) (ID, error) {
|
||||
parts := strings.Split(in, separator)
|
||||
if len(parts) != 2 {
|
||||
partType, partValue, ok := strings.Cut(in, separator)
|
||||
if !ok {
|
||||
return ID{}, ErrBadSeparator
|
||||
}
|
||||
partType := parts[0]
|
||||
partValue := parts[1]
|
||||
val, err := strconv.Atoi(partValue)
|
||||
if err != nil {
|
||||
return ID{}, fmt.Errorf("%q: %w", partValue, ErrNotAnInt)
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
)
|
||||
|
||||
func TestParseID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tcases := []struct {
|
||||
param string
|
||||
expType IDT
|
||||
@@ -20,9 +22,12 @@ func TestParseID(t *testing.T) {
|
||||
{param: "1", expErr: ErrBadSeparator},
|
||||
{param: "al-howdy", expErr: ErrNotAnInt},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
tcase := tcase // pin
|
||||
t.Run(tcase.param, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := New(tcase.param)
|
||||
if !errors.Is(err, tcase.expErr) {
|
||||
t.Fatalf("expected err %q, got %q", tcase.expErr, err)
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||
)
|
||||
|
||||
var ErrNotAbs = errors.New("not abs")
|
||||
var ErrNotFound = errors.New("not found")
|
||||
var (
|
||||
ErrNotAbs = errors.New("not abs")
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
type Result interface {
|
||||
SID() *specid.ID
|
||||
|
||||
Reference in New Issue
Block a user