mirror of
https://github.com/skidoodle/pastebin
synced 2026-04-28 03:07:40 +02:00
resolve dangliing hashes
This commit is contained in:
@@ -10,11 +10,6 @@ func notFound(slug string, err error, w http.ResponseWriter, r *http.Request) {
|
||||
respondWithError(slug, err, w, r, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// badRequest handles 400 Bad Request errors.
|
||||
func badRequest(slug string, err error, w http.ResponseWriter, r *http.Request) {
|
||||
respondWithError(slug, err, w, r, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// internal handles 500 Internal Server Error errors.
|
||||
func internal(slug string, err error, w http.ResponseWriter, r *http.Request) {
|
||||
respondWithError(slug, err, w, r, http.StatusInternalServerError)
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ func (h *HttpHandler) HandleSet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.store.Set(id, contentHash, content); err != nil {
|
||||
if err := h.store.Set(id, contentHash, content, nil); err != nil {
|
||||
internal("could not save bin", err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
+159
-4
@@ -1,7 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -31,8 +33,8 @@ func (m *MockStore) GetIDByHash(hash string) (string, bool, error) {
|
||||
return args.String(0), args.Bool(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockStore) Set(id, hash, content string) error {
|
||||
args := m.Called(id, hash, content)
|
||||
func (m *MockStore) Set(id, hash, content string, metadata map[string]interface{}) error {
|
||||
args := m.Called(id, hash, content, metadata)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ func TestHandleSet(t *testing.T) {
|
||||
content := "new content"
|
||||
ch := hash(content)
|
||||
s.On("GetIDByHash", ch).Return("", false, nil).Once()
|
||||
s.On("Set", mock.Anything, ch, content).Return(nil).Once()
|
||||
s.On("Set", mock.Anything, ch, content, mock.Anything).Return(nil).Once()
|
||||
|
||||
form := url.Values{"content": {content}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
@@ -120,7 +122,7 @@ func TestHandleSet(t *testing.T) {
|
||||
content := "error content"
|
||||
ch := hash(content)
|
||||
s.On("GetIDByHash", ch).Return("", false, nil).Once()
|
||||
s.On("Set", mock.Anything, ch, content).Return(errors.New("db error")).Once()
|
||||
s.On("Set", mock.Anything, ch, content, mock.Anything).Return(errors.New("db error")).Once()
|
||||
|
||||
form := url.Values{"content": {content}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
@@ -130,6 +132,44 @@ func TestHandleSet(t *testing.T) {
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("Generate ID Error", func(t *testing.T) {
|
||||
originalReader := rand.Reader
|
||||
defer func() { rand.Reader = originalReader }()
|
||||
rand.Reader = errorReader{}
|
||||
|
||||
content := "generate error content"
|
||||
ch := hash(content)
|
||||
s.On("GetIDByHash", ch).Return("", false, nil).Once()
|
||||
|
||||
form := url.Values{"content": {content}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("Malformed Form Data", func(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("content=%zz"))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid form data")
|
||||
})
|
||||
|
||||
t.Run("Too Large Content", func(t *testing.T) {
|
||||
content := strings.Repeat("a", 2048)
|
||||
form := url.Values{"content": {content}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Content too large")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleGet(t *testing.T) {
|
||||
@@ -167,6 +207,18 @@ func TestHandleGet(t *testing.T) {
|
||||
h.HandleGet(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("With Extension", func(t *testing.T) {
|
||||
id := "testid"
|
||||
s.On("Get", id).Return(&store.Paste{Content: "hello", CreatedAt: time.Now()}, true, nil).Once()
|
||||
req := httptest.NewRequest("GET", "/"+id+".go", nil)
|
||||
req.SetPathValue("id", id+".go")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.HandleGet(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "hello")
|
||||
})
|
||||
}
|
||||
|
||||
type FailingResponseWriter struct {
|
||||
@@ -184,6 +236,14 @@ func TestHandleHomeError(t *testing.T) {
|
||||
h.HandleHome(rr, req)
|
||||
}
|
||||
|
||||
type mockTemplateStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockTemplateStore) ExecuteTemplate(w http.ResponseWriter, name string, data interface{}) error {
|
||||
return errors.New("template error")
|
||||
}
|
||||
|
||||
func TestHandleRaw(t *testing.T) {
|
||||
s := new(MockStore)
|
||||
h := NewHandler(s, 1024, "../view/templates/*.html")
|
||||
@@ -202,6 +262,19 @@ func TestHandleRaw(t *testing.T) {
|
||||
assert.Equal(t, "text/plain; charset=utf-8", rr.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("With Extension", func(t *testing.T) {
|
||||
id := "testid"
|
||||
content := "raw content"
|
||||
s.On("Get", id).Return(&store.Paste{Content: content}, true, nil).Once()
|
||||
req := httptest.NewRequest("GET", "/raw/"+id+".txt", nil)
|
||||
req.SetPathValue("id", id+".txt")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.HandleRaw(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, content, rr.Body.String())
|
||||
})
|
||||
|
||||
t.Run("Not Found", func(t *testing.T) {
|
||||
s.On("Get", "missing").Return(nil, false, nil).Once()
|
||||
req := httptest.NewRequest("GET", "/raw/missing", nil)
|
||||
@@ -211,6 +284,16 @@ func TestHandleRaw(t *testing.T) {
|
||||
h.HandleRaw(rr, req)
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("Store Error", func(t *testing.T) {
|
||||
s.On("Get", "error").Return(nil, false, errors.New("db error")).Once()
|
||||
req := httptest.NewRequest("GET", "/raw/error", nil)
|
||||
req.SetPathValue("id", "error")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
h.HandleRaw(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleGetTemplateError(t *testing.T) {
|
||||
@@ -224,3 +307,75 @@ func TestHandleGetTemplateError(t *testing.T) {
|
||||
|
||||
h.HandleGet(rr, req)
|
||||
}
|
||||
|
||||
func TestHandleSetTemplateError(t *testing.T) {
|
||||
h := NewHandler(nil, 1024, "../view/templates/*.html")
|
||||
|
||||
t.Run("Empty Content", func(t *testing.T) {
|
||||
form := url.Values{"content": {""}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := &FailingResponseWriter{*httptest.NewRecorder()}
|
||||
h.HandleSet(rr, req)
|
||||
})
|
||||
|
||||
t.Run("Parse Error", func(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("content=%zz"))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := &FailingResponseWriter{*httptest.NewRecorder()}
|
||||
h.HandleSet(rr, req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateErrors(t *testing.T) {
|
||||
tmpl := template.New("empty")
|
||||
h := &HttpHandler{
|
||||
templates: tmpl,
|
||||
maxSize: 1024,
|
||||
}
|
||||
|
||||
t.Run("HandleHome Error", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleHome(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("HandleGet Error", func(t *testing.T) {
|
||||
s := new(MockStore)
|
||||
h.store = s
|
||||
id := "testid"
|
||||
s.On("Get", id).Return(&store.Paste{Content: "hello", CreatedAt: time.Now()}, true, nil).Once()
|
||||
req := httptest.NewRequest("GET", "/"+id, nil)
|
||||
req.SetPathValue("id", id)
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleGet(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("HandleSet Empty Content Error", func(t *testing.T) {
|
||||
form := url.Values{"content": {""}}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("HandleSet Parse Error", func(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("content=%zz"))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
rr := httptest.NewRecorder()
|
||||
h.HandleSet(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
})
|
||||
}
|
||||
func TestSafeHTML(t *testing.T) {
|
||||
h := NewHandler(nil, 1024, "../view/templates/*.html")
|
||||
tmpl, err := h.templates.New("test").Parse(`{{ safeHTML "<br>" }}`)
|
||||
assert.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
err = tmpl.Execute(rr, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "<br>", rr.Body.String())
|
||||
}
|
||||
|
||||
+10
-9
@@ -5,22 +5,23 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
func generateId() (string, error) {
|
||||
bytes := make([]byte, 10)
|
||||
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
|
||||
return "", err
|
||||
result := make([]byte, 10)
|
||||
max := big.NewInt(int64(len(charset)))
|
||||
for i := 0; i < 10; i++ {
|
||||
n, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result[i] = charset[n.Int64()]
|
||||
}
|
||||
|
||||
for i := range bytes {
|
||||
bytes[i] = charset[bytes[i]%byte(len(charset))]
|
||||
}
|
||||
return string(bytes), nil
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func TimeAgo(t time.Time) string {
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type errorReader struct{}
|
||||
|
||||
func (e errorReader) Read(p []byte) (n int, err error) {
|
||||
return 0, errors.New("read error")
|
||||
}
|
||||
|
||||
func TestGenerateId(t *testing.T) {
|
||||
id, err := generateId()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 10, len(id))
|
||||
}
|
||||
|
||||
func TestGenerateIdError(t *testing.T) {
|
||||
originalReader := rand.Reader
|
||||
defer func() { rand.Reader = originalReader }()
|
||||
rand.Reader = errorReader{}
|
||||
|
||||
id, err := generateId()
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, id)
|
||||
}
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
c := "test content"
|
||||
h1 := hash(c)
|
||||
|
||||
Reference in New Issue
Block a user