You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

120 lines
4.1 KiB

package web
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"webfilebrowser/internal/auth"
"webfilebrowser/internal/config"
)
func TestAuthenticatedBrowserListsRootsAndProvidesBreadcrumbNavigation(t *testing.T) {
rootPath, handler := newBrowsingApplication(t)
if err := os.Mkdir(filepath.Join(rootPath, "documents"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootPath, "documents", "report.txt"), []byte("report"), 0o600); err != nil {
t.Fatal(err)
}
cookie := completeSeedPasswordChange(t, handler)
rootResponse := getWithCookie(handler, "/", cookie)
if rootResponse.Code != http.StatusOK {
t.Fatalf("GET / status = %d, want %d", rootResponse.Code, http.StatusOK)
}
if body := rootResponse.Body.String(); !strings.Contains(body, "Media") || !strings.Contains(body, "/browse?root=Media") {
t.Errorf("GET / did not render the configured root: %s", body)
}
directoryResponse := getWithCookie(handler, "/browse?root=Media&path=documents", cookie)
if directoryResponse.Code != http.StatusOK {
t.Fatalf("GET nested directory status = %d, want %d", directoryResponse.Code, http.StatusOK)
}
body := directoryResponse.Body.String()
for _, text := range []string{"Media", "documents", "report.txt", "File"} {
if !strings.Contains(body, text) {
t.Errorf("GET nested directory body is missing %q: %s", text, body)
}
}
}
func TestBrowserRejectsTraversalAndSymlinkEscapeRequests(t *testing.T) {
rootPath, handler := newBrowsingApplication(t)
outsidePath := t.TempDir()
if err := os.WriteFile(filepath.Join(outsidePath, "secret.txt"), []byte("outside-root"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outsidePath, filepath.Join(rootPath, "escape")); err != nil {
t.Fatal(err)
}
cookie := completeSeedPasswordChange(t, handler)
for _, target := range []string{"/browse?root=Media&path=../outside", "/browse?root=Media&path=escape"} {
t.Run(target, func(t *testing.T) {
response := getWithCookie(handler, target, cookie)
if response.Code != http.StatusNotFound {
t.Errorf("GET %s status = %d, want %d", target, response.Code, http.StatusNotFound)
}
if strings.Contains(response.Body.String(), "outside-root") {
t.Errorf("GET %s exposed content outside the configured root", target)
}
})
}
}
func TestBrowserThemeTogglePersistsInLocalStorage(t *testing.T) {
_, handler := newBrowsingApplication(t)
cookie := completeSeedPasswordChange(t, handler)
response := getWithCookie(handler, "/", cookie)
if response.Code != http.StatusOK {
t.Fatalf("GET / status = %d, want %d", response.Code, http.StatusOK)
}
body := response.Body.String()
for _, text := range []string{`id="theme-toggle"`, "localStorage.getItem", "localStorage.setItem", "data-theme"} {
if !strings.Contains(body, text) {
t.Errorf("GET / is missing theme persistence behavior %q", text)
}
}
}
func newBrowsingApplication(t *testing.T) (string, http.Handler) {
t.Helper()
rootPath := t.TempDir()
store, err := auth.Open(filepath.Join(t.TempDir(), "webfilebrowser.db"), auth.Options{})
if err != nil {
t.Fatalf("Open() error = %v", err)
}
t.Cleanup(func() { _ = store.Close() })
return rootPath, New(store, config.Root{Name: "Media", Path: rootPath})
}
func completeSeedPasswordChange(t *testing.T, handler http.Handler) *http.Cookie {
t.Helper()
loginResponse := postForm(handler, loginPath, url.Values{
"email": {auth.SeedAdminEmail},
"password": {auth.SeedAdminPassword},
}, nil)
cookie := responseCookie(t, loginResponse, sessionCookieName)
changeResponse := postForm(handler, changePasswordPath, url.Values{
"current_password": {auth.SeedAdminPassword},
"new_password": {"A-new-password-123!"},
}, cookie)
if changeResponse.Code != http.StatusSeeOther {
t.Fatalf("POST %s status = %d, want %d", changePasswordPath, changeResponse.Code, http.StatusSeeOther)
}
return cookie
}
func getWithCookie(handler http.Handler, target string, cookie *http.Cookie) *httptest.ResponseRecorder {
request := httptest.NewRequest(http.MethodGet, target, nil)
request.AddCookie(cookie)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
return response
}