Files

259 lines
7.7 KiB
Go
Raw Permalink Normal View History

package handlers
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"sort"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/Silo-Server/silo-server/internal/access"
)
func TestAccessGroupHandlerIsDefaultRoundTrips(t *testing.T) {
store := newAccessGroupHandlerTestStore()
handler := NewAccessGroupHandler(store)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/access-groups", strings.NewReader(`{
"name": "Users",
"is_default": true
}`))
handler.HandleCreate(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("HandleCreate status = %d, body %s", rec.Code, rec.Body.String())
}
created := decodeAccessGroupResponse(t, rec)
if !created.IsDefault {
t.Fatalf("created is_default = false, want true")
}
rec = httptest.NewRecorder()
req = accessGroupRequestWithID(http.MethodGet, "/api/v1/admin/access-groups/1", nil, "1")
handler.HandleGet(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("HandleGet status = %d, body %s", rec.Code, rec.Body.String())
}
got := decodeAccessGroupResponse(t, rec)
if !got.IsDefault {
t.Fatalf("get is_default = false, want true")
}
}
func TestAccessGroupHandlerUpdateDefaultUnsetsPrevious(t *testing.T) {
store := newAccessGroupHandlerTestStore()
store.groups[1] = access.Group{ID: 1, Name: "Group A", DownloadAllowed: true, RequestsAllowed: true, IsDefault: true}
store.groups[2] = access.Group{ID: 2, Name: "Group B", DownloadAllowed: true, RequestsAllowed: true}
store.nextID = 3
handler := NewAccessGroupHandler(store)
rec := httptest.NewRecorder()
req := accessGroupRequestWithID(http.MethodPut, "/api/v1/admin/access-groups/2", strings.NewReader(`{
"is_default": true
}`), "2")
handler.HandleUpdate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("HandleUpdate status = %d, body %s", rec.Code, rec.Body.String())
}
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/access-groups", nil)
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("HandleList status = %d, body %s", rec.Code, rec.Body.String())
}
var groups []accessGroupResponse
if err := json.NewDecoder(rec.Body).Decode(&groups); err != nil {
t.Fatalf("decode list: %v", err)
}
defaults := map[int64]bool{}
for _, group := range groups {
defaults[group.ID] = group.IsDefault
}
if defaults[1] {
t.Fatalf("group A remained default after setting group B")
}
if !defaults[2] {
t.Fatalf("group B is_default = false, want true")
}
}
func TestAccessGroupHandlerDefaultGroupGuards(t *testing.T) {
store := newAccessGroupHandlerTestStore()
store.groups[1] = access.Group{ID: 1, Name: "Default", DownloadAllowed: true, RequestsAllowed: true, IsDefault: true}
store.nextID = 2
handler := NewAccessGroupHandler(store)
rec := httptest.NewRecorder()
req := accessGroupRequestWithID(http.MethodDelete, "/api/v1/admin/access-groups/1", nil, "1")
handler.HandleDelete(rec, req)
if rec.Code != http.StatusConflict {
t.Fatalf("HandleDelete(default) status = %d, want %d, body %s", rec.Code, http.StatusConflict, rec.Body.String())
}
if _, ok := store.groups[1]; !ok {
t.Fatalf("default group removed despite conflict response")
}
rec = httptest.NewRecorder()
req = accessGroupRequestWithID(http.MethodPut, "/api/v1/admin/access-groups/1", strings.NewReader(`{
"is_default": false
}`), "1")
handler.HandleUpdate(rec, req)
if rec.Code != http.StatusConflict {
t.Fatalf("HandleUpdate(demote default) status = %d, want %d, body %s", rec.Code, http.StatusConflict, rec.Body.String())
}
if !store.groups[1].IsDefault {
t.Fatalf("default group demoted despite conflict response")
}
}
type accessGroupHandlerTestStore struct {
nextID int64
groups map[int64]access.Group
}
func newAccessGroupHandlerTestStore() *accessGroupHandlerTestStore {
return &accessGroupHandlerTestStore{
nextID: 1,
groups: map[int64]access.Group{},
}
}
func (s *accessGroupHandlerTestStore) List(context.Context) ([]access.Group, error) {
groups := make([]access.Group, 0, len(s.groups))
for _, group := range s.groups {
groups = append(groups, group)
}
sort.Slice(groups, func(i, j int) bool {
return groups[i].ID < groups[j].ID
})
return groups, nil
}
func (s *accessGroupHandlerTestStore) Get(_ context.Context, id int64) (*access.Group, error) {
group, ok := s.groups[id]
if !ok {
return nil, access.ErrGroupNotFound
}
return &group, nil
}
func (s *accessGroupHandlerTestStore) Create(_ context.Context, input access.CreateGroupInput) (*access.Group, error) {
now := time.Unix(1, 0).UTC()
group := access.Group{
ID: s.nextID,
Name: input.Name,
Description: input.Description,
LibraryIDs: append([]int(nil), input.LibraryIDs...),
MaxPlaybackQuality: input.MaxPlaybackQuality,
DownloadAllowed: input.DownloadAllowed,
DownloadTranscodeAllowed: input.DownloadTranscodeAllowed,
MaxStreams: input.MaxStreams,
MaxTranscodes: input.MaxTranscodes,
AllowedPermissions: append([]string(nil), input.AllowedPermissions...),
RequestsAllowed: input.RequestsAllowed,
IsDefault: input.IsDefault,
CreatedAt: now,
UpdatedAt: now,
}
if input.IsDefault {
s.clearDefault()
}
s.groups[group.ID] = group
s.nextID++
return &group, nil
}
func (s *accessGroupHandlerTestStore) Update(_ context.Context, id int64, input access.UpdateGroupInput) (*access.Group, error) {
group, ok := s.groups[id]
if !ok {
return nil, access.ErrGroupNotFound
}
if input.Name != nil {
group.Name = *input.Name
}
if input.Description != nil {
group.Description = *input.Description
}
if input.LibraryIDs != nil {
group.LibraryIDs = append([]int(nil), (*input.LibraryIDs)...)
}
if input.MaxPlaybackQuality != nil {
group.MaxPlaybackQuality = *input.MaxPlaybackQuality
}
if input.DownloadAllowed != nil {
group.DownloadAllowed = *input.DownloadAllowed
}
if input.DownloadTranscodeAllowed != nil {
group.DownloadTranscodeAllowed = *input.DownloadTranscodeAllowed
}
if input.MaxStreams != nil {
group.MaxStreams = *input.MaxStreams
}
if input.MaxTranscodes != nil {
group.MaxTranscodes = *input.MaxTranscodes
}
if input.AllowedPermissions != nil {
group.AllowedPermissions = append([]string(nil), (*input.AllowedPermissions)...)
}
if input.RequestsAllowed != nil {
group.RequestsAllowed = *input.RequestsAllowed
}
if input.IsDefault != nil {
if *input.IsDefault {
s.clearDefault()
} else if group.IsDefault {
return nil, access.ErrDefaultGroupRequired
}
group.IsDefault = *input.IsDefault
}
group.UpdatedAt = time.Unix(2, 0).UTC()
s.groups[id] = group
return &group, nil
}
func (s *accessGroupHandlerTestStore) Delete(_ context.Context, id int64) error {
group, ok := s.groups[id]
if !ok {
return access.ErrGroupNotFound
}
if group.IsDefault {
return access.ErrDefaultGroupRequired
}
delete(s.groups, id)
return nil
}
func (s *accessGroupHandlerTestStore) clearDefault() {
for id, group := range s.groups {
group.IsDefault = false
s.groups[id] = group
}
}
func accessGroupRequestWithID(method, path string, body *strings.Reader, id string) *http.Request {
var reader io.Reader
if body != nil {
reader = body
}
req := httptest.NewRequest(method, path, reader)
routeCtx := chi.NewRouteContext()
routeCtx.URLParams.Add("id", id)
return req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
}
func decodeAccessGroupResponse(t *testing.T, rec *httptest.ResponseRecorder) accessGroupResponse {
t.Helper()
var response accessGroupResponse
if err := json.NewDecoder(rec.Body).Decode(&response); err != nil {
t.Fatalf("decode response: %v", err)
}
return response
}