mirror of
https://github.com/lukaszraczylo/gohoarder.git
synced 2026-06-10 23:29:22 +00:00
fixes
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/cache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestNewBaseHandler tests base handler creation
|
||||
func TestNewBaseHandler(t *testing.T) {
|
||||
// Use nil for cache and client since we're only testing structure
|
||||
handler := NewBaseHandler(nil, nil, "npm", "https://registry.npmjs.org")
|
||||
|
||||
require.NotNil(t, handler)
|
||||
assert.Equal(t, "npm", handler.Registry)
|
||||
assert.Equal(t, "https://registry.npmjs.org", handler.Upstream)
|
||||
assert.Nil(t, handler.Cache)
|
||||
assert.Nil(t, handler.Client)
|
||||
}
|
||||
|
||||
// TestGetRegistry tests registry type retrieval
|
||||
func TestGetRegistry(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
registry string
|
||||
}{
|
||||
{"npm registry", "npm"},
|
||||
{"pypi registry", "pypi"},
|
||||
{"go registry", "go"},
|
||||
{"custom registry", "custom"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := &BaseHandler{Registry: tt.registry}
|
||||
assert.Equal(t, tt.registry, handler.GetRegistry())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleUpstreamError tests upstream error handling
|
||||
func TestHandleUpstreamError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
url string
|
||||
context string
|
||||
wantStatus int
|
||||
wantContain string
|
||||
}{
|
||||
// GOOD: Standard error
|
||||
{
|
||||
name: "connection error",
|
||||
err: errors.New("connection refused"),
|
||||
url: "https://registry.npmjs.org/react",
|
||||
context: "package",
|
||||
wantStatus: http.StatusBadGateway,
|
||||
wantContain: "Failed to fetch package",
|
||||
},
|
||||
// WRONG: Timeout error
|
||||
{
|
||||
name: "timeout error",
|
||||
err: context.DeadlineExceeded,
|
||||
url: "https://registry.npmjs.org/lodash",
|
||||
context: "metadata",
|
||||
wantStatus: http.StatusBadGateway,
|
||||
wantContain: "Failed to fetch metadata",
|
||||
},
|
||||
// EDGE: Empty context
|
||||
{
|
||||
name: "empty context",
|
||||
err: errors.New("error"),
|
||||
url: "https://example.com",
|
||||
context: "",
|
||||
wantStatus: http.StatusBadGateway,
|
||||
wantContain: "Failed to fetch",
|
||||
},
|
||||
// EDGE: Long URL
|
||||
{
|
||||
name: "long URL",
|
||||
err: errors.New("error"),
|
||||
url: "https://registry.npmjs.org/@scope/very-long-package-name/versions/1.2.3",
|
||||
context: "package",
|
||||
wantStatus: http.StatusBadGateway,
|
||||
wantContain: "Failed to fetch package",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
HandleUpstreamError(w, tt.err, tt.url, tt.context)
|
||||
|
||||
assert.Equal(t, tt.wantStatus, w.Code)
|
||||
assert.Contains(t, w.Body.String(), tt.wantContain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckUpstreamStatus tests upstream status validation
|
||||
func TestCheckUpstreamStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
body io.ReadCloser
|
||||
wantErr bool
|
||||
errContains string
|
||||
bodyClosed bool
|
||||
}{
|
||||
// GOOD: OK status
|
||||
{
|
||||
name: "200 OK",
|
||||
statusCode: http.StatusOK,
|
||||
body: io.NopCloser(strings.NewReader("success")),
|
||||
wantErr: false,
|
||||
},
|
||||
// WRONG: Not found
|
||||
{
|
||||
name: "404 Not Found",
|
||||
statusCode: http.StatusNotFound,
|
||||
body: io.NopCloser(strings.NewReader("not found")),
|
||||
wantErr: true,
|
||||
errContains: "upstream returned status 404",
|
||||
},
|
||||
// WRONG: Server error
|
||||
{
|
||||
name: "500 Internal Server Error",
|
||||
statusCode: http.StatusInternalServerError,
|
||||
body: io.NopCloser(strings.NewReader("error")),
|
||||
wantErr: true,
|
||||
errContains: "upstream returned status 500",
|
||||
},
|
||||
// BAD: Unauthorized
|
||||
{
|
||||
name: "401 Unauthorized",
|
||||
statusCode: http.StatusUnauthorized,
|
||||
body: io.NopCloser(strings.NewReader("unauthorized")),
|
||||
wantErr: true,
|
||||
errContains: "upstream returned status 401",
|
||||
},
|
||||
// EDGE: Nil body
|
||||
{
|
||||
name: "nil body with error",
|
||||
statusCode: http.StatusNotFound,
|
||||
body: nil,
|
||||
wantErr: true,
|
||||
errContains: "upstream returned status 404",
|
||||
},
|
||||
// EDGE: Redirect status
|
||||
{
|
||||
name: "302 Found",
|
||||
statusCode: http.StatusFound,
|
||||
body: io.NopCloser(strings.NewReader("redirect")),
|
||||
wantErr: true,
|
||||
errContains: "upstream returned status 302",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CheckUpstreamStatus(tt.statusCode, tt.body)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleInvalidRequest tests invalid request handling
|
||||
func TestHandleInvalidRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
registry string
|
||||
wantStatus int
|
||||
wantContain string
|
||||
}{
|
||||
{
|
||||
name: "npm invalid request",
|
||||
registry: "npm",
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantContain: "Invalid npm request",
|
||||
},
|
||||
{
|
||||
name: "pypi invalid request",
|
||||
registry: "pypi",
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantContain: "Invalid pypi request",
|
||||
},
|
||||
{
|
||||
name: "go invalid request",
|
||||
registry: "go",
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantContain: "Invalid go request",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
HandleInvalidRequest(w, tt.registry)
|
||||
|
||||
assert.Equal(t, tt.wantStatus, w.Code)
|
||||
assert.Contains(t, w.Body.String(), tt.wantContain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleInternalError tests internal error handling
|
||||
func TestHandleInternalError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
context string
|
||||
wantStatus int
|
||||
wantContain string
|
||||
}{
|
||||
{
|
||||
name: "database error",
|
||||
err: errors.New("database connection failed"),
|
||||
context: "database",
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantContain: "Internal error: database",
|
||||
},
|
||||
{
|
||||
name: "cache error",
|
||||
err: errors.New("cache write failed"),
|
||||
context: "cache",
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantContain: "Internal error: cache",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
HandleInternalError(w, tt.err, tt.context)
|
||||
|
||||
assert.Equal(t, tt.wantStatus, w.Code)
|
||||
assert.Contains(t, w.Body.String(), tt.wantContain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Note: FetchFromUpstream tests would require mocking cache.Manager and network.Client
|
||||
// which requires concrete implementations. Integration tests cover this functionality.
|
||||
|
||||
// TestWriteResponse tests HTTP response writing
|
||||
func TestWriteResponse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
contentType string
|
||||
wantStatus int
|
||||
wantBody string
|
||||
wantErr bool
|
||||
}{
|
||||
// GOOD: Write tarball
|
||||
{
|
||||
name: "write tarball",
|
||||
data: "package data here",
|
||||
contentType: "application/octet-stream",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "package data here",
|
||||
wantErr: false,
|
||||
},
|
||||
// GOOD: Write JSON
|
||||
{
|
||||
name: "write JSON metadata",
|
||||
data: `{"name":"react","version":"18.2.0"}`,
|
||||
contentType: "application/json",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: `{"name":"react","version":"18.2.0"}`,
|
||||
wantErr: false,
|
||||
},
|
||||
// EDGE: Empty data
|
||||
{
|
||||
name: "empty data",
|
||||
data: "",
|
||||
contentType: "text/plain",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "",
|
||||
wantErr: false,
|
||||
},
|
||||
// EDGE: Large data
|
||||
{
|
||||
name: "large data",
|
||||
data: strings.Repeat("x", 100000),
|
||||
contentType: "application/octet-stream",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: strings.Repeat("x", 100000),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
entry := &cache.CacheEntry{
|
||||
Data: io.NopCloser(bytes.NewReader([]byte(tt.data))),
|
||||
}
|
||||
|
||||
err := WriteResponse(w, entry, tt.contentType)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.contentType, w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, tt.wantBody, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseHandlerFields tests that BaseHandler fields are properly set
|
||||
func TestBaseHandlerFields(t *testing.T) {
|
||||
handler := NewBaseHandler(nil, nil, "npm", "https://registry.npmjs.org")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field string
|
||||
expected interface{}
|
||||
}{
|
||||
{"registry field", "registry", "npm"},
|
||||
{"upstream field", "upstream", "https://registry.npmjs.org"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
switch tt.field {
|
||||
case "registry":
|
||||
assert.Equal(t, tt.expected, handler.Registry)
|
||||
case "upstream":
|
||||
assert.Equal(t, tt.expected, handler.Upstream)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProxyHandlerInterface tests that BaseHandler can be used as ProxyHandler
|
||||
func TestProxyHandlerInterface(t *testing.T) {
|
||||
handler := NewBaseHandler(nil, nil, "npm", "https://registry.npmjs.org")
|
||||
|
||||
// Verify GetRegistry works
|
||||
registry := handler.GetRegistry()
|
||||
assert.Equal(t, "npm", registry)
|
||||
}
|
||||
|
||||
// TestConcurrentWriteResponse tests that WriteResponse is safe for concurrent use
|
||||
func TestConcurrentWriteResponse(t *testing.T) {
|
||||
const numGoroutines = 10
|
||||
|
||||
errs := make(chan error, numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(n int) {
|
||||
w := httptest.NewRecorder()
|
||||
data := strings.Repeat("x", 1000)
|
||||
entry := &cache.CacheEntry{
|
||||
Data: io.NopCloser(bytes.NewReader([]byte(data))),
|
||||
}
|
||||
|
||||
err := WriteResponse(w, entry, "text/plain")
|
||||
errs <- err
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Collect results
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
err := <-errs
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user