db17557d0d
* added spawn scripts * wip * Make server not require length * Added test for 0 length * Added test for accepting length of 0 * linter errors * Make store not take anything more than an id and maybe a ttl if you want
430 lines
10 KiB
Go
430 lines
10 KiB
Go
// Copyright (C) 2018 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"testing"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"storj.io/storj/pkg/piecestore"
|
|
"storj.io/storj/pkg/piecestore/rpc/server/ttl"
|
|
pb "storj.io/storj/protos/piecestore"
|
|
)
|
|
|
|
var tempDir string = path.Join(os.TempDir(), "test-data", "3000")
|
|
var tempDBPath string = path.Join(os.TempDir(), "test.db")
|
|
var db *sql.DB
|
|
var s Server
|
|
var c pb.PieceStoreRoutesClient
|
|
var testId string = "11111111111111111111"
|
|
var testCreatedDate int64 = 1234567890
|
|
var testExpiration int64 = 9999999999
|
|
|
|
func TestPiece(t *testing.T) {
|
|
// simulate piece stored with farmer
|
|
file, err := pstore.StoreWriter(testId, s.PieceStoreDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Close when finished
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(file, bytes.NewReader([]byte("butts")))
|
|
|
|
if err != nil {
|
|
t.Errorf("Error: %v\nCould not create test piece", err)
|
|
return
|
|
}
|
|
|
|
defer pstore.Delete(testId, s.PieceStoreDir)
|
|
|
|
// set up test cases
|
|
tests := []struct {
|
|
id string
|
|
size int64
|
|
expiration int64
|
|
err string
|
|
}{
|
|
{ // should successfully retrieve piece meta-data
|
|
id: testId,
|
|
size: 5,
|
|
expiration: testExpiration,
|
|
err: "",
|
|
},
|
|
{ // server should err with invalid id
|
|
id: "123",
|
|
size: 5,
|
|
expiration: testExpiration,
|
|
err: "rpc error: code = Unknown desc = argError: Invalid id length",
|
|
},
|
|
{ // server should err with nonexistent file
|
|
id: "22222222222222222222",
|
|
size: 5,
|
|
expiration: testExpiration,
|
|
err: fmt.Sprintf("rpc error: code = Unknown desc = stat %s: no such file or directory", path.Join(os.TempDir(), "/test-data/3000/22/22/2222222222222222")),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("should return expected PieceSummary values", func(t *testing.T) {
|
|
|
|
// simulate piece TTL entry
|
|
_, err = db.Exec(fmt.Sprintf(`INSERT INTO ttl (id, created, expires) VALUES ("%s", "%d", "%d")`, tt.id, testCreatedDate, testExpiration))
|
|
if err != nil {
|
|
t.Errorf("Error: %v\nCould not make TTL entry", err)
|
|
return
|
|
}
|
|
|
|
defer db.Exec(fmt.Sprintf(`DELETE FROM ttl WHERE id="%s"`, tt.id))
|
|
|
|
req := &pb.PieceId{Id: tt.id}
|
|
resp, err := c.Piece(context.Background(), req)
|
|
|
|
if len(tt.err) > 0 {
|
|
|
|
if err != nil {
|
|
if err.Error() == tt.err {
|
|
return
|
|
}
|
|
}
|
|
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
|
|
if err != nil && tt.err == "" {
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
|
|
if resp.Id != tt.id || resp.Size != tt.size || resp.Expiration != tt.expiration {
|
|
t.Errorf("Expected: %v, %v, %v\nGot: %v, %v, %v\n", tt.id, tt.size, tt.expiration, resp.Id, resp.Size, resp.Expiration)
|
|
return
|
|
}
|
|
|
|
// clean up DB entry
|
|
_, err = db.Exec(fmt.Sprintf(`DELETE FROM ttl WHERE id="%s"`, testId))
|
|
if err != nil {
|
|
t.Errorf("Error cleaning test DB entry")
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetrieve(t *testing.T) {
|
|
// simulate piece stored with farmer
|
|
file, err := pstore.StoreWriter(testId, s.PieceStoreDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Close when finished
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(file, bytes.NewReader([]byte("butts")))
|
|
if err != nil {
|
|
t.Errorf("Error: %v\nCould not create test piece", err)
|
|
return
|
|
}
|
|
defer pstore.Delete(testId, s.PieceStoreDir)
|
|
|
|
// set up test cases
|
|
tests := []struct {
|
|
id string
|
|
reqSize int64
|
|
respSize int64
|
|
offset int64
|
|
content []byte
|
|
err string
|
|
}{
|
|
{ // should successfully retrieve data
|
|
id: testId,
|
|
reqSize: 5,
|
|
respSize: 5,
|
|
offset: 0,
|
|
content: []byte("butts"),
|
|
err: "",
|
|
},
|
|
{ // server should err with invalid id
|
|
id: "123",
|
|
reqSize: 5,
|
|
respSize: 5,
|
|
offset: 0,
|
|
content: []byte("butts"),
|
|
err: "rpc error: code = Unknown desc = argError: Invalid id length",
|
|
},
|
|
{ // server should err with nonexistent file
|
|
id: "22222222222222222222",
|
|
reqSize: 5,
|
|
respSize: 5,
|
|
offset: 0,
|
|
content: []byte("butts"),
|
|
err: fmt.Sprintf("rpc error: code = Unknown desc = stat %s: no such file or directory", path.Join(os.TempDir(), "/test-data/3000/22/22/2222222222222222")),
|
|
},
|
|
{ // server should return expected content and respSize with offset and excess reqSize
|
|
id: testId,
|
|
reqSize: 5,
|
|
respSize: 4,
|
|
offset: 1,
|
|
content: []byte("utts"),
|
|
err: "",
|
|
},
|
|
{ // server should return expected content with reduced reqSize
|
|
id: testId,
|
|
reqSize: 4,
|
|
respSize: 4,
|
|
offset: 0,
|
|
content: []byte("butt"),
|
|
err: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("should return expected PieceRetrievalStream values", func(t *testing.T) {
|
|
req := &pb.PieceRetrieval{Id: tt.id, Size: tt.reqSize, Offset: tt.offset}
|
|
stream, err := c.Retrieve(context.Background(), req)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
resp, err := stream.Recv()
|
|
if len(tt.err) > 0 {
|
|
if err != nil {
|
|
if err.Error() == tt.err {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
if err != nil && tt.err == "" {
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
|
|
if resp.Size != tt.respSize || bytes.Equal(resp.Content, tt.content) != true {
|
|
t.Errorf("Expected: %v, %v\nGot: %v, %v\n", tt.respSize, tt.content, resp.Size, resp.Content)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStore(t *testing.T) {
|
|
tests := []struct {
|
|
id string
|
|
size int64
|
|
ttl int64
|
|
offset int64
|
|
content []byte
|
|
message string
|
|
totalReceived int64
|
|
err string
|
|
}{
|
|
{ // should successfully store data
|
|
id: testId,
|
|
ttl: testExpiration,
|
|
content: []byte("butts"),
|
|
message: "OK",
|
|
totalReceived: 5,
|
|
err: "",
|
|
},
|
|
{ // should err with invalid id length
|
|
id: "butts",
|
|
ttl: testExpiration,
|
|
content: []byte("butts"),
|
|
message: "",
|
|
totalReceived: 0,
|
|
err: "rpc error: code = Unknown desc = argError: Invalid id length",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("should return expected PieceStoreSummary values", func(t *testing.T) {
|
|
|
|
stream, err := c.Store(context.Background())
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Write the buffer to the stream we opened earlier
|
|
if err := stream.Send(&pb.PieceStore{Id: tt.id, Ttl: tt.ttl}); err != nil {
|
|
t.Errorf("Unexpected error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Write the buffer to the stream we opened earlier
|
|
if err := stream.Send(&pb.PieceStore{Content: tt.content}); err != nil {
|
|
t.Errorf("Unexpected error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
resp, err := stream.CloseAndRecv()
|
|
|
|
defer db.Exec(fmt.Sprintf(`DELETE FROM ttl WHERE id="%s"`, tt.id))
|
|
|
|
if len(tt.err) > 0 {
|
|
if err != nil {
|
|
if err.Error() == tt.err {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
if err != nil && tt.err == "" {
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
|
|
if resp.Message != tt.message || resp.TotalReceived != tt.totalReceived {
|
|
t.Errorf("Expected: %v, %v\nGot: %v, %v\n", tt.message, tt.totalReceived, resp.Message, resp.TotalReceived)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
// set up test cases
|
|
tests := []struct {
|
|
id string
|
|
message string
|
|
err string
|
|
}{
|
|
{ // should successfully delete data
|
|
id: testId,
|
|
message: "OK",
|
|
err: "",
|
|
},
|
|
{ // should err with invalid id length
|
|
id: "123",
|
|
message: "rpc error: code = Unknown desc = argError: Invalid id length",
|
|
err: "rpc error: code = Unknown desc = argError: Invalid id length",
|
|
},
|
|
{ // should return OK with nonexistent file
|
|
id: "22222222222222222223",
|
|
message: "OK",
|
|
err: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("should return expected PieceDeleteSummary values", func(t *testing.T) {
|
|
|
|
// simulate piece stored with farmer
|
|
file, err := pstore.StoreWriter(testId, s.PieceStoreDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Close when finished
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(file, bytes.NewReader([]byte("butts")))
|
|
if err != nil {
|
|
t.Errorf("Error: %v\nCould not create test piece", err)
|
|
return
|
|
}
|
|
|
|
// simulate piece TTL entry
|
|
_, err = db.Exec(fmt.Sprintf(`INSERT INTO ttl (id, created, expires) VALUES ("%s", "%d", "%d")`, tt.id, testCreatedDate, testCreatedDate))
|
|
if err != nil {
|
|
t.Errorf("Error: %v\nCould not make TTL entry", err)
|
|
return
|
|
}
|
|
|
|
defer db.Exec(fmt.Sprintf(`DELETE FROM ttl WHERE id="%s"`, tt.id))
|
|
|
|
defer pstore.Delete(testId, s.PieceStoreDir)
|
|
|
|
req := &pb.PieceDelete{Id: tt.id}
|
|
resp, err := c.Delete(context.Background(), req)
|
|
if len(tt.err) > 0 {
|
|
if err != nil {
|
|
if err.Error() == tt.err {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
if err != nil && tt.err == "" {
|
|
t.Errorf("\nExpected: %s\nGot: %v\n", tt.err, err)
|
|
return
|
|
}
|
|
|
|
if resp.Message != tt.message {
|
|
t.Errorf("Expected: %v\nGot: %v\n", tt.message, resp.Message)
|
|
return
|
|
}
|
|
|
|
// if test passes, check if file was indeed deleted
|
|
filePath, err := pstore.PathByID(tt.id, s.PieceStoreDir)
|
|
if _, err = os.Stat(filePath); os.IsNotExist(err) != true {
|
|
t.Errorf("File not deleted")
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func StartServer() {
|
|
lis, err := net.Listen("tcp", ":3000")
|
|
if err != nil {
|
|
log.Fatalf("failed to listen: %v", err)
|
|
}
|
|
grpcs := grpc.NewServer()
|
|
pb.RegisterPieceStoreRoutesServer(grpcs, &s)
|
|
if err := grpcs.Serve(lis); err != nil {
|
|
log.Fatalf("failed to serve: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
go StartServer()
|
|
|
|
// Set up a connection to the Server.
|
|
const address = "localhost:3000"
|
|
conn, err := grpc.Dial(address, grpc.WithInsecure())
|
|
if err != nil {
|
|
fmt.Printf("did not connect: %v", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
c = pb.NewPieceStoreRoutesClient(conn)
|
|
|
|
ttlDB, err := ttl.NewTTL(tempDBPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
s = Server{tempDir, ttlDB}
|
|
|
|
db = ttlDB.DB
|
|
|
|
// clean up temp files
|
|
defer os.RemoveAll(path.Join(tempDir, "/test-data"))
|
|
defer os.Remove(tempDBPath)
|
|
defer db.Close()
|
|
|
|
m.Run()
|
|
}
|