From 360a6869060c4b0e96ec3a7fbefe8013d4e90a4a Mon Sep 17 00:00:00 2001 From: breel Date: Sun, 9 Aug 2020 13:16:28 -0600 Subject: [PATCH] impl many integration tests --- main.go | 4 +- main_test.go | 319 +++++++++++++++++++++++++++++++++++++ server/auth/generate.go | 4 +- server/auth/token.go | 7 +- server/auth/token_test.go | 2 +- server/auth/verify.go | 4 +- server/auth/verify_test.go | 8 +- server/rest.go | 2 +- 8 files changed, 338 insertions(+), 12 deletions(-) create mode 100644 main_test.go diff --git a/main.go b/main.go index ac1b6e2..0ba241f 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,9 @@ import ( "log" ) -var GitCommit string +var ( + GitCommit string +) func main() { c := config.New() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a52b07c --- /dev/null +++ b/main_test.go @@ -0,0 +1,319 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "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 = strings.Split(fmt.Sprintf(`dndex -auth=true -database db -delay 5ms -driver-type 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.Error("not impl") +} + +func createUpdateSub(t *testing.T, uri, token string) { + t.Error("not impl") +} + +func filesCRUD(t *testing.T, uri, token string) { + t.Error("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) { + id := callCreate(t, uri, token) + callUpdate(t, uri, token, id) +} + +func createDelete(t *testing.T, uri, token string) { + 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) { + 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) + } + var one entity.One + if err := json.NewDecoder(resp.Body).Decode(&one); err != nil { + t.Fatal(err) + } + return one +} + +func callDelete(t *testing.T, uri, token, id string) { + 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) { + 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{ + "???": map[string]string{"relationship": ":?"}, + }, + "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 fmt.Sprint(m[k2]) != fmt.Sprint(response[k][k2]) { + t.Fatal(k2, response[k][k2]) + } + } + found = true + } + if !found { + t.Fatal(found) + } + } +} + +func callCreate(t *testing.T, uri, token string) string { + 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{ + "???": map[string]string{"relationship": ":?"}, + }, + "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) { + 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 +} diff --git a/server/auth/generate.go b/server/auth/generate.go index 46df2f1..0b2fdd5 100644 --- a/server/auth/generate.go +++ b/server/auth/generate.go @@ -61,14 +61,14 @@ func getKeyForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespa func makeTokenForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (Token, error) { token := Token{ Namespace: namespace, - Token: uuid.New().String(), + ID: uuid.New().String(), } obf, err := token.Obfuscate() if err != nil { return Token{}, err } one := entity.One{ - ID: token.Token, + ID: token.ID, Title: obf, } return token, g.Insert(ctx, namespace+"."+AuthKey, one) diff --git a/server/auth/token.go b/server/auth/token.go index 0112009..26f5835 100644 --- a/server/auth/token.go +++ b/server/auth/token.go @@ -7,7 +7,12 @@ import ( type Token struct { Namespace string - Token string + ID string +} + +func (t Token) String() string { + s, _ := t.Obfuscate() + return s } func (t Token) Obfuscate() (string, error) { diff --git a/server/auth/token_test.go b/server/auth/token_test.go index e797178..9fbafd4 100644 --- a/server/auth/token_test.go +++ b/server/auth/token_test.go @@ -10,7 +10,7 @@ import ( func TestTokenEncDec(t *testing.T) { token := Token{ Namespace: "username", - Token: uuid.New().String(), + ID: uuid.New().String(), } key := "a" diff --git a/server/auth/verify.go b/server/auth/verify.go index 6f0f1b2..83d3fac 100644 --- a/server/auth/verify.go +++ b/server/auth/verify.go @@ -29,7 +29,7 @@ func getToken(r *http.Request) (Token, bool) { if !config.New().Auth { namespaces, ok := r.URL.Query()["ns"] if ok && len(namespaces) > 0 { - return Token{Namespace: namespaces[0], Token: uuid.New().String()}, true + return Token{Namespace: namespaces[0], ID: uuid.New().String()}, true } } cookie, err := r.Cookie(AuthKey) @@ -55,7 +55,7 @@ func isPublicNamespace(ctx context.Context, g storage.RateLimitedGraph, namespac } func verifyToken(token Token, g storage.RateLimitedGraph, r *http.Request) error { - serverTokenContainer, err := g.Get(r.Context(), token.Namespace+"."+AuthKey, token.Token) + serverTokenContainer, err := g.Get(r.Context(), token.Namespace+"."+AuthKey, token.ID) if err != nil { return err } diff --git a/server/auth/verify_test.go b/server/auth/verify_test.go index 65aa394..21af40e 100644 --- a/server/auth/verify_test.go +++ b/server/auth/verify_test.go @@ -21,12 +21,12 @@ func TestVerify(t *testing.T) { fresh := func() (storage.RateLimitedGraph, *httptest.ResponseRecorder, *http.Request, Token, string) { g := storage.NewRateLimitedGraph() token := Token{ - Token: uuid.New().String(), + ID: uuid.New().String(), Namespace: uuid.New().String(), } obf, _ := token.Obfuscate() one := entity.One{ - ID: token.Token, + ID: token.ID, Title: obf, } if err := g.Insert(context.TODO(), token.Namespace+"."+AuthKey, one); err != nil { @@ -102,7 +102,7 @@ func TestVerify(t *testing.T) { t.Run("bad auth", func(t *testing.T) { g, w, r, token, _ := fresh() - token.Token = uuid.New().String() + token.ID = uuid.New().String() obf, err := token.Obfuscate() if err != nil { t.Fatal(err) @@ -133,7 +133,7 @@ func TestVerify(t *testing.T) { if err := g.Insert(context.TODO(), token.Namespace, entity.One{ID: UserKey}); err != nil { t.Fatal(err) } - token.Token = "gibberish-but-public-ns-so-its-ok" + token.ID = "gibberish-but-public-ns-so-its-ok" obf, _ := token.Obfuscate() r.AddCookie(&http.Cookie{ Name: AuthKey, diff --git a/server/rest.go b/server/rest.go index e1800ce..5c852aa 100644 --- a/server/rest.go +++ b/server/rest.go @@ -45,7 +45,7 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) { bar := foo bar = rest.shift(bar) bar = rest.scoped(bar) - if !strings.HasPrefix(path, "users/") { + if !strings.HasPrefix(path, "users/") && path != "version" { bar = rest.auth(bar) } bar = rest.defend(bar)