Files
dolt/go/datas/remote_database_handlers_test.go
T
2017-01-10 15:45:05 -08:00

379 lines
10 KiB
Go

// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package datas
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/attic-labs/noms/go/chunks"
"github.com/attic-labs/noms/go/hash"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/testify/assert"
"github.com/golang/snappy"
)
func TestHandleWriteValue(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
db := NewDatabase(cs)
l := types.NewList(
db.WriteValue(types.Bool(true)),
db.WriteValue(types.Bool(false)),
)
r := db.WriteValue(l)
_, err := db.CommitValue(db.GetDataset("datasetID"), r)
assert.NoError(err)
hint := l.Hash()
newItem := types.NewEmptyBlob()
itemChunk := types.EncodeValue(newItem, nil)
l2 := l.Insert(1, types.NewRef(newItem))
listChunk := types.EncodeValue(l2, nil)
body := &bytes.Buffer{}
serializeHints(body, map[hash.Hash]struct{}{hint: {}})
chunks.Serialize(itemChunk, body)
chunks.Serialize(listChunk, body)
w := httptest.NewRecorder()
HandleWriteValue(w, newRequest("POST", "", "", body, nil), params{}, cs)
if assert.Equal(http.StatusCreated, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
db2 := NewDatabase(cs)
v := db2.ReadValue(l2.Hash())
if assert.NotNil(v) {
assert.True(v.Equals(l2), "%+v != %+v", v, l2)
}
}
}
func TestHandleWriteValuePanic(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
body := &bytes.Buffer{}
serializeHints(body, types.Hints{})
body.WriteString("Bogus")
w := httptest.NewRecorder()
HandleWriteValue(w, newRequest("POST", "", "", body, nil), params{}, cs)
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
}
func TestHandleWriteValueDupChunks(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
newItem := types.NewEmptyBlob()
itemChunk := types.EncodeValue(newItem, nil)
body := &bytes.Buffer{}
serializeHints(body, map[hash.Hash]struct{}{})
// Write the same chunk to body enough times to be certain that at least one of the concurrent deserialize/decode passes completes before the last one can continue.
for i := 0; i <= writeValueConcurrency; i++ {
chunks.Serialize(itemChunk, body)
}
w := httptest.NewRecorder()
HandleWriteValue(w, newRequest("POST", "", "", body, nil), params{}, cs)
if assert.Equal(http.StatusCreated, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
db := NewDatabase(cs)
v := db.ReadValue(newItem.Hash())
if assert.NotNil(v) {
assert.True(v.Equals(newItem), "%+v != %+v", v, newItem)
}
}
}
func TestHandleWriteValueBackpressure(t *testing.T) {
assert := assert.New(t)
cs := &backpressureCS{ChunkStore: chunks.NewMemoryStore()}
db := NewDatabase(cs)
l := types.NewList(
db.WriteValue(types.Bool(true)),
db.WriteValue(types.Bool(false)),
)
r := db.WriteValue(l)
_, err := db.CommitValue(db.GetDataset("datasetID"), r)
assert.NoError(err)
hint := l.Hash()
newItem := types.NewEmptyBlob()
itemChunk := types.EncodeValue(newItem, nil)
l2 := l.Insert(1, types.NewRef(newItem))
listChunk := types.EncodeValue(l2, nil)
body := &bytes.Buffer{}
serializeHints(body, map[hash.Hash]struct{}{hint: {}})
chunks.Serialize(itemChunk, body)
chunks.Serialize(listChunk, body)
w := httptest.NewRecorder()
HandleWriteValue(w, newRequest("POST", "", "", body, nil), params{}, cs)
if assert.Equal(http.StatusTooManyRequests, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
hashes := deserializeHashes(w.Body)
assert.Len(hashes, 1)
assert.Equal(l2.Hash(), hashes[0])
}
}
func TestBuildWriteValueRequest(t *testing.T) {
assert := assert.New(t)
input1, input2 := "abc", "def"
chnx := []chunks.Chunk{
chunks.NewChunk([]byte(input1)),
chunks.NewChunk([]byte(input2)),
}
inChunkChan := make(chan *chunks.Chunk, 2)
inChunkChan <- &chnx[0]
inChunkChan <- &chnx[1]
close(inChunkChan)
hints := map[hash.Hash]struct{}{
hash.Parse("00000000000000000000000000000002"): {},
hash.Parse("00000000000000000000000000000003"): {},
}
compressed := buildWriteValueRequest(inChunkChan, hints)
gr := snappy.NewReader(compressed)
count := 0
for hint := range deserializeHints(gr) {
count++
_, present := hints[hint]
assert.True(present)
}
assert.Equal(len(hints), count)
outChunkChan := make(chan *chunks.Chunk, len(chnx))
chunks.Deserialize(gr, outChunkChan)
close(outChunkChan)
for c := range outChunkChan {
assert.Equal(chnx[0].Hash(), c.Hash())
chnx = chnx[1:]
}
assert.Empty(chnx)
}
func serializeChunks(chnx []chunks.Chunk, assert *assert.Assertions) io.Reader {
body := &bytes.Buffer{}
sw := snappy.NewBufferedWriter(body)
for _, chunk := range chnx {
chunks.Serialize(chunk, sw)
}
assert.NoError(sw.Close())
return body
}
func TestBuildHashesRequest(t *testing.T) {
assert := assert.New(t)
hashes := map[hash.Hash]struct{}{
hash.Parse("00000000000000000000000000000002"): {},
hash.Parse("00000000000000000000000000000003"): {},
}
r := buildHashesRequest(hashes)
b, err := ioutil.ReadAll(r)
assert.NoError(err)
urlValues, err := url.ParseQuery(string(b))
assert.NoError(err)
assert.NotEmpty(urlValues)
queryRefs := urlValues["ref"]
assert.Len(queryRefs, len(hashes))
for _, r := range queryRefs {
_, present := hashes[hash.Parse(r)]
assert.True(present, "Query contains %s, which is not in initial refs", r)
}
}
func TestHandleGetRefs(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
input1, input2 := "abc", "def"
chnx := []chunks.Chunk{
chunks.NewChunk([]byte(input1)),
chunks.NewChunk([]byte(input2)),
}
err := cs.PutMany(chnx)
assert.NoError(err)
body := strings.NewReader(fmt.Sprintf("ref=%s&ref=%s", chnx[0].Hash(), chnx[1].Hash()))
w := httptest.NewRecorder()
HandleGetRefs(
w,
newRequest("POST", "", "", body, http.Header{
"Content-Type": {"application/x-www-form-urlencoded"},
}),
params{},
cs,
)
if assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
chunkChan := make(chan *chunks.Chunk, len(chnx))
chunks.Deserialize(w.Body, chunkChan)
close(chunkChan)
foundHashes := hash.HashSet{}
for c := range chunkChan {
foundHashes[c.Hash()] = struct{}{}
}
assert.True(len(foundHashes) == 2)
_, hasC1 := foundHashes[chnx[0].Hash()]
assert.True(hasC1)
_, hasC2 := foundHashes[chnx[1].Hash()]
assert.True(hasC2)
}
}
func TestHandleHasRefs(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
input1, input2 := "abc", "def"
chnx := []chunks.Chunk{
chunks.NewChunk([]byte(input1)),
chunks.NewChunk([]byte(input2)),
}
err := cs.PutMany(chnx)
assert.NoError(err)
absent := hash.Parse("00000000000000000000000000000002")
body := strings.NewReader(fmt.Sprintf("ref=%s&ref=%s&ref=%s", chnx[0].Hash(), chnx[1].Hash(), absent))
w := httptest.NewRecorder()
HandleHasRefs(
w,
newRequest("POST", "", "", body, http.Header{
"Content-Type": {"application/x-www-form-urlencoded"},
}),
params{},
cs,
)
if assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
scanner := bufio.NewScanner(w.Body)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
h := hash.Parse(scanner.Text())
scanner.Scan()
if scanner.Text() == "true" {
assert.Equal(chnx[0].Hash(), h)
chnx = chnx[1:]
} else {
assert.Equal(absent, h)
}
}
assert.Empty(chnx)
}
}
func TestHandleGetRoot(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
c := chunks.NewChunk([]byte("abc"))
cs.Put(c)
assert.True(cs.UpdateRoot(c.Hash(), hash.Hash{}))
w := httptest.NewRecorder()
HandleRootGet(w, newRequest("GET", "", "", nil, nil), params{}, cs)
if assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
root := hash.Parse(string(w.Body.Bytes()))
assert.Equal(c.Hash(), root)
}
}
func TestHandleGetBase(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
c := chunks.NewChunk([]byte("abc"))
cs.Put(c)
assert.True(cs.UpdateRoot(c.Hash(), hash.Hash{}))
w := httptest.NewRecorder()
HandleBaseGet(w, newRequest("GET", "", "", nil, nil), params{}, cs)
if assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
assert.Equal([]byte(nomsBaseHTML), w.Body.Bytes())
}
}
func TestHandlePostRoot(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
vs := types.NewValueStore(types.NewBatchStoreAdaptor(cs))
commit := NewCommit(types.String("head"), types.NewSet(), types.NewStruct("Meta", types.StructData{}))
newHead := types.NewMap(types.String("dataset1"), vs.WriteValue(commit))
chnx := []chunks.Chunk{
chunks.NewChunk([]byte("abc")),
types.EncodeValue(newHead, nil),
}
err := cs.PutMany(chnx)
assert.NoError(err)
// First attempt should fail, as 'last' won't match.
u := &url.URL{}
queryParams := url.Values{}
queryParams.Add("last", chnx[0].Hash().String())
queryParams.Add("current", chnx[1].Hash().String())
u.RawQuery = queryParams.Encode()
url := u.String()
w := httptest.NewRecorder()
HandleRootPost(w, newRequest("POST", "", url, nil, nil), params{}, cs)
assert.Equal(http.StatusConflict, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
// Now, update the root manually to 'last' and try again.
assert.True(cs.UpdateRoot(chnx[0].Hash(), hash.Hash{}))
w = httptest.NewRecorder()
HandleRootPost(w, newRequest("POST", "", url, nil, nil), params{}, cs)
assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
}
func TestRejectPostRoot(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()
newHead := types.NewMap(types.String("dataset1"), types.String("Not a Head"))
chunk := types.EncodeValue(newHead, nil)
cs.Put(chunk)
// First attempt should fail, as 'last' won't match.
u := &url.URL{}
queryParams := url.Values{}
queryParams.Add("last", chunks.EmptyChunk.Hash().String())
queryParams.Add("current", chunk.Hash().String())
u.RawQuery = queryParams.Encode()
url := u.String()
w := httptest.NewRecorder()
HandleRootPost(w, newRequest("POST", "", url, nil, nil), params{}, cs)
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
}
type params map[string]string
func (p params) ByName(k string) string {
return p[k]
}