407 lines
10 KiB
Go
407 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"local/dndex/config"
|
|
"local/dndex/server/auth"
|
|
"local/dndex/storage/entity"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func Test(t *testing.T) {
|
|
GitCommit = uuid.New().String()
|
|
d, err := ioutil.TempDir(os.TempDir(), "test.dndex")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := httptest.NewServer(http.HandlerFunc(nil))
|
|
s.Close()
|
|
p := strings.Split(s.URL, ":")[2]
|
|
os.Args = []string{"a"}
|
|
s.URL = s.URL + "/" + config.New().APIPrefix
|
|
os.Args = strings.Split(fmt.Sprintf(`dndex -auth=true -database db -delay 5ms -driver map -fileprefix /files -fileroot %s -p %v -rps 50 -sys-rps 40`, d, p), " ")
|
|
|
|
go main()
|
|
|
|
for {
|
|
resp, err := http.Get(s.URL)
|
|
if err == nil {
|
|
resp.Body.Close()
|
|
break
|
|
}
|
|
time.Sleep(time.Millisecond * 100)
|
|
}
|
|
|
|
token := register(t, s.URL)
|
|
t.Logf("token: %q", token)
|
|
createDelete(t, s.URL, token)
|
|
createUpdate(t, s.URL, token)
|
|
callVersion(t, s.URL)
|
|
muckedToken(t, s.URL, token)
|
|
createDeleteSub(t, s.URL, token)
|
|
createUpdateSub(t, s.URL, token)
|
|
filesCRUD(t, s.URL, token)
|
|
}
|
|
|
|
func createDeleteSub(t *testing.T, uri, token string) {
|
|
t.Run("create, delete sub", func(t *testing.T) {
|
|
id := callCreate(t, uri, token, "")
|
|
was := callGet(t, uri, token, id, http.StatusOK)
|
|
callDelete(t, uri, token, id+"/title")
|
|
one := callGet(t, uri, token, id, http.StatusOK)
|
|
if one.Title != "" {
|
|
t.Fatal(one.Title)
|
|
}
|
|
was.Title = ""
|
|
was.Modified = 0
|
|
one.Modified = 0
|
|
if fmt.Sprint(one) != fmt.Sprint(was) {
|
|
t.Fatalf("partial del failed: want \n%+v, got \n%+v", was, one)
|
|
}
|
|
})
|
|
}
|
|
|
|
func createUpdateSub(t *testing.T, uri, token string) {
|
|
t.Run("create, update sub", func(t *testing.T) {
|
|
id := callCreate(t, uri, token, "")
|
|
was := callGet(t, uri, token, id, http.StatusOK)
|
|
resp := call(t, token, http.MethodPut, uri+"/entities/"+id+"/title", `"newtitle"`)
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
one := callGet(t, uri, token, id, http.StatusOK)
|
|
if one.Title != "newtitle" {
|
|
t.Fatal(one.Title)
|
|
}
|
|
was.Title = "newtitle"
|
|
was.Modified = 0
|
|
one.Modified = 0
|
|
if fmt.Sprint(one) != fmt.Sprint(was) {
|
|
t.Fatalf("partial updated failed: want \n%+v, got \n%+v", was, one)
|
|
}
|
|
})
|
|
}
|
|
|
|
func filesCRUD(t *testing.T, uri, token string) {
|
|
t.Run("files CRUD", func(t *testing.T) {
|
|
t.Log("not impl")
|
|
})
|
|
}
|
|
|
|
func register(t *testing.T, uri string) string {
|
|
callList(t, uri, "", http.StatusUnauthorized)
|
|
ns, pwd := callRegister(t, uri)
|
|
token := callLogin(t, uri, ns, pwd)
|
|
callList(t, uri, token, http.StatusOK)
|
|
return token
|
|
}
|
|
|
|
func muckedToken(t *testing.T, uri, obf string) {
|
|
var token auth.Token
|
|
if err := token.Deobfuscate(obf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if token.Namespace == "" || token.ID == "" {
|
|
t.Fatalf("ns: %q, id: %q", token.Namespace, token.ID)
|
|
}
|
|
namespace := token.Namespace
|
|
id := token.ID
|
|
for _, token := range []auth.Token{
|
|
auth.Token{
|
|
Namespace: namespace,
|
|
ID: uuid.New().String(),
|
|
},
|
|
auth.Token{
|
|
Namespace: uuid.New().String(),
|
|
ID: id,
|
|
},
|
|
} {
|
|
callList(t, uri, token.String(), http.StatusUnauthorized)
|
|
}
|
|
}
|
|
|
|
func createUpdate(t *testing.T, uri, token string) {
|
|
t.Run("create, update", func(t *testing.T) {
|
|
id := callCreate(t, uri, token, "")
|
|
callUpdate(t, uri, token, id)
|
|
})
|
|
}
|
|
|
|
func createDelete(t *testing.T, uri, token string) {
|
|
t.Run("create, delete", func(t *testing.T) {
|
|
id := callCreate(t, uri, token, "")
|
|
callDelete(t, uri, token, id)
|
|
one := callGet(t, uri, token, id, http.StatusNotFound)
|
|
if one.ID == id {
|
|
t.Fatal(one)
|
|
}
|
|
if fmt.Sprint(one) != fmt.Sprint(entity.One{}) {
|
|
t.Fatal(one)
|
|
}
|
|
})
|
|
}
|
|
|
|
func callVersion(t *testing.T, uri string) {
|
|
t.Run("call /version", func(t *testing.T) {
|
|
resp := call(t, "", http.MethodGet, uri+"/version", "")
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
var response struct {
|
|
Version string `json:"version"`
|
|
}
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(b, &response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if response.Version != GitCommit {
|
|
t.Fatalf("%s: %v", b, response)
|
|
}
|
|
})
|
|
}
|
|
|
|
func callGet(t *testing.T, uri, token, id string, status int) entity.One {
|
|
resp := call(t, token, http.MethodGet, uri+"/entities/"+id, "")
|
|
if resp.StatusCode != status {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
if status != http.StatusOK {
|
|
return entity.One{}
|
|
}
|
|
b, _ := ioutil.ReadAll(resp.Body)
|
|
var response map[string]entity.One
|
|
if err := json.Unmarshal(b, &response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, one := range response {
|
|
if fmt.Sprint(one) == fmt.Sprint(entity.One{}) {
|
|
t.Fatal(id, status, one, string(b))
|
|
}
|
|
return one
|
|
}
|
|
t.Fatal("no items in response")
|
|
return entity.One{}
|
|
}
|
|
|
|
func callDelete(t *testing.T, uri, token, id string) {
|
|
t.Run("call delete /entities/X", func(t *testing.T) {
|
|
resp := call(t, token, http.MethodDelete, uri+"/entities/"+id, "")
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
})
|
|
}
|
|
|
|
func callUpdate(t *testing.T, uri, token, id string) {
|
|
t.Run("call update /entities/X", func(t *testing.T) {
|
|
for _, method := range []string{http.MethodPut, http.MethodPatch} {
|
|
m := map[string]interface{}{
|
|
"name": "name-" + uuid.New().String()[:5],
|
|
"type": "type-" + uuid.New().String()[:5],
|
|
"title": "titl-" + uuid.New().String()[:5],
|
|
"text": "text-" + uuid.New().String()[:5],
|
|
"connections": map[string]map[string]string{
|
|
callCreate(t, uri, token, ""): map[string]string{"relationship": "callUpdate peer"},
|
|
},
|
|
"attachments": map[string]map[string]string{
|
|
"myfile": map[string]string{"location": "/files/my_file_location.txt"},
|
|
},
|
|
}
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp := call(t, token, method, uri+"/entities/"+id, string(b))
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
var response map[string]map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for k := range response {
|
|
for k2 := range m {
|
|
if k2 == entity.Connections {
|
|
if v, ok := response[k][k2]; !ok {
|
|
t.Fatal("missing connection", k2)
|
|
} else if m2, ok := v.(map[string]interface{}); !ok {
|
|
t.Fatalf("wrong connection type %T", v)
|
|
} else {
|
|
wantConnections := m[k2].(map[string]map[string]string)
|
|
for k3 := range wantConnections {
|
|
wantRelationship := wantConnections[k3][entity.Relationship]
|
|
gotRelationship := m2[k3].(map[string]interface{})[entity.Relationship]
|
|
if wantRelationship != gotRelationship {
|
|
t.Fatal(gotRelationship, wantRelationship)
|
|
}
|
|
b, _ := json.Marshal(m2[k3])
|
|
var gotOne entity.One
|
|
json.Unmarshal(b, &gotOne)
|
|
if k3 != gotOne.ID {
|
|
t.Fatal(gotOne.ID)
|
|
}
|
|
if "" == gotOne.Text {
|
|
t.Fatal(gotOne)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if fmt.Sprint(m[k2]) != fmt.Sprint(response[k][k2]) {
|
|
t.Fatalf("@%q.%q, want %q, got %q", k, k2, fmt.Sprint(m[k2]), fmt.Sprint(response[k][k2]))
|
|
}
|
|
}
|
|
}
|
|
found = true
|
|
}
|
|
if !found {
|
|
t.Fatal(found)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func callCreate(t *testing.T, uri, token, peerID string) string {
|
|
connections := map[string]map[string]string{}
|
|
if peerID != "" {
|
|
connections[peerID] = map[string]string{"relationship": "call Create peer"}
|
|
}
|
|
m := map[string]interface{}{
|
|
"name": "name-" + uuid.New().String()[:5],
|
|
"type": "type-" + uuid.New().String()[:5],
|
|
"title": "titl-" + uuid.New().String()[:5],
|
|
"text": "text-" + uuid.New().String()[:5],
|
|
"connections": connections,
|
|
"attachments": map[string]map[string]string{
|
|
"myfile": map[string]string{"location": "/files/my_file_location.txt"},
|
|
},
|
|
}
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp := call(t, token, http.MethodPost, uri+"/entities", string(b))
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
var response map[string]map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for k := range response {
|
|
for k2 := range m {
|
|
if fmt.Sprint(m[k2]) != fmt.Sprint(response[k][k2]) {
|
|
t.Fatal(k2, response[k][k2])
|
|
}
|
|
}
|
|
return k
|
|
}
|
|
t.Fatal(response)
|
|
panic("how?")
|
|
}
|
|
|
|
func callList(t *testing.T, uri, token string, status int) {
|
|
t.Run("call get /entities", func(t *testing.T) {
|
|
resp := call(t, token, http.MethodGet, uri+"/entities", "")
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp.StatusCode != status {
|
|
t.Fatalf("%v: %s", resp.StatusCode, b)
|
|
}
|
|
})
|
|
}
|
|
|
|
func callRegister(t *testing.T, uri string) (string, string) {
|
|
ns := uuid.New().String()
|
|
pwd := uuid.New().String()
|
|
|
|
resp, err := http.Post(uri+"/users/register", "application/x-www-form-urlencoded", strings.NewReader(fmt.Sprintf("%s=%s&%s=%s", auth.UserKey, ns, auth.AuthKey, pwd)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
|
|
return ns, pwd
|
|
}
|
|
|
|
func callLogin(t *testing.T, uri, ns, pwd string) string {
|
|
resp, err := http.Post(uri+"/users/login", "application/x-www-form-urlencoded", strings.NewReader(fmt.Sprintf("%s=%s", auth.UserKey, ns)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatal(resp.StatusCode)
|
|
}
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var response struct {
|
|
OK struct {
|
|
EncodedToken string `json:"token"`
|
|
Salt string `json:"salt"`
|
|
} `json:"ok"`
|
|
}
|
|
if err := json.Unmarshal(b, &response); err != nil {
|
|
t.Fatalf("%v: %s", err, b)
|
|
}
|
|
salt := response.OK.Salt
|
|
encodedToken := response.OK.EncodedToken
|
|
if len(salt) == 0 {
|
|
t.Fatal("salt empty")
|
|
}
|
|
if len(encodedToken) == 0 {
|
|
t.Fatal("token empty")
|
|
}
|
|
token := auth.Token{}
|
|
if err := token.Decode(salt+pwd, encodedToken); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return token.String()
|
|
}
|
|
|
|
func call(t *testing.T, token, method, uri, body string) *http.Response {
|
|
r, err := http.NewRequest(method, uri, strings.NewReader(body))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.AddCookie(&http.Cookie{Name: auth.AuthKey, Value: token})
|
|
resp, err := http.DefaultClient.Do(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp.Body = struct {
|
|
io.Reader
|
|
io.Closer
|
|
}{
|
|
Closer: resp.Body,
|
|
Reader: bytes.NewReader(b),
|
|
}
|
|
return resp
|
|
}
|