Files
backer/internal/backer/newServer_test.go
eleksir 16ae887196 Add AGENTS.md, simplify code, 2 fixes in log package, 3 bugs fixed.
- Add AGENTS.md for vibecoding ai data.
- Remove github.com/pkg/errors dependency
- writeWithContext — remove goroutine overhead, just check context before fn
- copyWithContext — remove unnecessary panic recovery
- Remove panic recovery tests (TestWriteWithContextPanic, TestCopyWithContextPanic, panicReader)
- Error, Warn, Info, Debug, Fatal — accept ...any instead of string (enables log.Error(err))
- Close — don't panic if Log is nil, don't close stderr
- Add unit tests (internal/log/main_test.go)
- getClientIP — trim spaces from X-Forwarded-For, use net.SplitHostPort for IPv6
- isExcluded — normalize paths to forward slashes for cross-platform regex matching
- writeWithContext godoc — fix stale comment about panic recovery
- internal/log/main_test.go — log package tests (19 tests)
2026-03-27 01:24:32 +03:00

342 lines
8.4 KiB
Go

package backer
import (
"archive/tar"
"compress/gzip"
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// TestNewServerSuccess tests successful server creation.
func TestNewServerSuccess(t *testing.T) {
C = Config{} // Reset global config.
server, err := NewServer("./../../test_data/test_config.json")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if server == nil {
t.Fatal("Expected non-nil server")
}
if server.Addr != "127.0.0.1:8086" {
t.Errorf("Expected address '127.0.0.1:8086', got '%s'", server.Addr)
}
}
// TestNewServerInvalidConfig tests server creation with invalid config.
func TestNewServerInvalidConfig(t *testing.T) {
C = Config{} // Reset global config.
_, err := NewServer("/nonexistent/config.json")
if err == nil {
t.Error("Expected error for invalid config path, got nil")
}
}
// TestHandlerNoAuth tests that requests without auth are rejected.
func TestHandlerNoAuth(t *testing.T) {
C = Config{
Address: "0.0.0.0",
Port: 8086,
Location: "/archive",
User: "testuser",
Password: "testpass",
Directories: []string{"../../test_data/test1/foo"},
}
excludePatterns = nil
req := httptest.NewRequest("GET", "/archive", nil)
w := httptest.NewRecorder()
// Create a handler that mimics the server's handler
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if !(username == C.User && password == C.Password) {
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
handler.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", w.Code)
}
authHeader := w.Header().Get("WWW-Authenticate")
if !strings.Contains(authHeader, "Basic") {
t.Errorf("Expected WWW-Authenticate header with Basic, got '%s'", authHeader)
}
}
// TestHandlerWrongCredentials tests that requests with wrong credentials are rejected.
func TestHandlerWrongCredentials(t *testing.T) {
C = Config{
Address: "0.0.0.0",
Port: 8086,
Location: "/archive",
User: "testuser",
Password: "testpass",
Directories: []string{"../../test_data/test1/foo"},
}
excludePatterns = nil
req := httptest.NewRequest("GET", "/archive", nil)
req.SetBasicAuth("wronguser", "wrongpass")
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if !(username == C.User && password == C.Password) {
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
handler.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", w.Code)
}
}
// TestHandlerCorrectCredentials tests that requests with correct credentials succeed.
func TestHandlerCorrectCredentials(t *testing.T) {
C = Config{
Address: "0.0.0.0",
Port: 8086,
Location: "/archive",
User: "testuser",
Password: "testpass",
Directories: []string{"../../test_data/test1/foo"},
}
excludePatterns = nil
req := httptest.NewRequest("GET", "/archive", nil)
req.SetBasicAuth("testuser", "testpass")
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if !(username == C.User && password == C.Password) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
}
// TestHandlerArchiveStream tests that the handler streams a valid tar.gz archive.
func TestHandlerArchiveStream(t *testing.T) {
C = Config{
Address: "0.0.0.0",
Port: 8086,
Location: "/archive",
User: "testuser",
Password: "testpass",
Directories: []string{"../../test_data/test1/foo"},
}
excludePatterns = nil
// Create a test server with the actual handler logic
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || !(username == C.User && password == C.Password) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := r.Context()
files, err := GetFilesFromDirectories(C.Directories)
if err != nil {
http.Error(w, "Failed to get files", http.StatusInternalServerError)
return
}
tarGz := CreateTarGzStream(ctx, files)
defer tarGz.Close()
w.Header().Set("Content-Disposition", "attachment; filename=archive.tar")
w.Header().Set("Content-Type", "application/octet-stream")
io.Copy(w, tarGz)
}))
defer server.Close()
// Make authenticated request
req, _ := http.NewRequest("GET", server.URL, nil)
req.SetBasicAuth("testuser", "testpass")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
// Verify it's a valid tar.gz
gzReader, err := gzip.NewReader(resp.Body)
if err != nil {
t.Fatalf("Failed to create gzip reader: %v", err)
}
defer gzReader.Close()
tarReader := tar.NewReader(gzReader)
foundFile := false
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("Failed to read tar entry: %v", err)
}
if strings.Contains(header.Name, "hello_breakfast.txt") {
foundFile = true
}
}
if !foundFile {
t.Error("Expected to find hello_breakfast.txt in archive")
}
}
// TestWriteWithContextSuccess tests successful write with context.
func TestWriteWithContextSuccess(t *testing.T) {
ctx := context.Background()
called := false
err := writeWithContext(ctx, func() error {
called = true
return nil
})
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if !called {
t.Error("Expected function to be called")
}
}
// TestWriteWithContextError tests write with context returning error.
func TestWriteWithContextError(t *testing.T) {
ctx := context.Background()
testErr := io.ErrUnexpectedEOF
err := writeWithContext(ctx, func() error {
return testErr
})
if err != testErr {
t.Errorf("Expected %v, got %v", testErr, err)
}
}
// TestWriteWithContextCancellation tests write with cancelled context.
func TestWriteWithContextCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
err := writeWithContext(ctx, func() error {
t.Error("Function should not be called with cancelled context")
return nil
})
if err != context.Canceled {
t.Errorf("Expected context.Canceled, got %v", err)
}
}
// TestCopyWithContextSuccess tests successful copy with context.
func TestCopyWithContextSuccess(t *testing.T) {
ctx := context.Background()
src := strings.NewReader("hello world")
dst := &strings.Builder{}
err := copyWithContext(ctx, dst, src)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if dst.String() != "hello world" {
t.Errorf("Expected 'hello world', got '%s'", dst.String())
}
}
// TestCopyWithContextCancellation tests copy with cancelled context.
func TestCopyWithContextCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
// Create a reader with a lot of data that will take time to copy
data := make([]byte, 1024*1024) // 1MB
for i := range data {
data[i] = byte(i % 256)
}
src := strings.NewReader(string(data))
dst := &strings.Builder{}
// Start copy in goroutine
errCh := make(chan error, 1)
go func() {
errCh <- copyWithContext(ctx, dst, src)
}()
// Cancel context immediately
cancel()
err := <-errCh
if err != context.Canceled {
t.Errorf("Expected context.Canceled, got %v", err)
}
}
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */