diff --git a/transport/http_test.go b/transport/http_test.go index c94fcd4..9359de2 100644 --- a/transport/http_test.go +++ b/transport/http_test.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -45,11 +46,16 @@ func TestInvites(t *testing.T) { server := store.Server{ Id: "1", + Rcon: store.Rcon{ + Address: "foo", + Password: "bar", + }, } inv := store.Invite{ Token: "foo", Server: server, + Creator: user, Uses: 0, Unlimited: false, } @@ -63,6 +69,12 @@ func TestInvites(t *testing.T) { Unlimited: false, } + invLog := store.InviteLog{ + EntryID: "f", + Invite: store.Invite{Token: inv.Token}, + User: store.User{Id: user.Id}, + } + invPayload := invitePayload{ Server: "1", Uses: 0, @@ -336,4 +348,406 @@ func TestInvites(t *testing.T) { } }) + t.Run("invite log returns logs", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + st.EXPECT().InviteLog(inv).Return([]store.InviteLog{invLog}, nil) + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo/log", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id/log", handler.InviteLog) + m.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + var expected []store.InviteLog + err = json.Unmarshal(rr.Body.Bytes(), &expected) + if err != nil { + t.Fatal(err) + } + if expected[0].EntryID != invLog.EntryID { + t.Errorf("handler returned unexpected entry id: got %v want %v", + rr.Body.String(), invLog.EntryID) + } + }) + + t.Run("invite log returns no logs", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + st.EXPECT().InviteLog(inv).Return([]store.InviteLog{}, nil) + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo/log", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id/log", handler.InviteLog) + m.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusNotFound { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusNotFound) + } + + expected := "[]" + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + +} + +func TestUser(t *testing.T) { + ctrl := gomock.NewController(t) + st := mock_store.NewMockStorer(ctrl) + im := mock_invite.NewMockInviteManager(ctrl) + mc := mock_minecraft.NewMockMinecraft(ctrl) + + user := store.User{ + Id: "1", + Token: "", + DisplayName: "user", + RefreshToken: "", + TokenExpiry: time.Time{}, + } + + t.Run("return username", func(t *testing.T) { + m := flow.New() + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("GET", "/api/v1/me", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/me", handler.CurrentUser) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + if rr.Body.String() != user.DisplayName { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), user.DisplayName) + } + }) +} + +func TestServers(t *testing.T) { + ctrl := gomock.NewController(t) + st := mock_store.NewMockStorer(ctrl) + im := mock_invite.NewMockInviteManager(ctrl) + mc := mock_minecraft.NewMockMinecraft(ctrl) + + user := store.User{ + Id: "1", + Token: "", + DisplayName: "user", + RefreshToken: "", + TokenExpiry: time.Time{}, + } + + server := store.Server{ + Id: "1", + Owner: user, + } + + inv := store.Invite{ + Token: "foo", + Server: server, + Uses: 0, + Unlimited: false, + } + + t.Run("fetch a user's servers", func(t *testing.T) { + st.EXPECT().GetUserServers(user).Return([]store.Server{server}, nil) + + m := flow.New() + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("GET", "/api/v1/servers", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/servers", handler.Servers) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := "[{\"id\":\"1\",\"name\":\"\",\"address\":\"\",\"rcon\":{}}]\n" + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + + t.Run("create a server for a user", func(t *testing.T) { + st.EXPECT().SaveServer(gomock.Any()).Return(nil) + + jsonData, err := json.Marshal(server) + if err != nil { + log.Fatal(err) + } + + m := flow.New() + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("POST", "/api/v1/servers", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/servers", handler.Servers) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + if !strings.Contains(rr.Body.String(), "created server") { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), "created server") + } + }) + + t.Run("get invite list for server", func(t *testing.T) { + st.EXPECT().GetServer(server.Id).Return(server, nil) + st.EXPECT().ServerInvites(server).Return([]store.Invite{inv}, nil) + + jsonData, err := json.Marshal(server) + if err != nil { + log.Fatal(err) + } + + m := flow.New() + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("POST", "/api/v1/server/1", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/server/:id", handler.ServerInvites) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + expected := "[{\"token\":\"foo\",\"creator\":{\"display_name\":\"\"},\"server\":{\"id\":\"1\",\"name\":\"\",\"address\":\"\",\"rcon\":{}},\"uses\":0,\"unlimited\":false}]\n" + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + + t.Run("get empty invite list for server", func(t *testing.T) { + st.EXPECT().GetServer(server.Id).Return(server, nil) + st.EXPECT().ServerInvites(server).Return([]store.Invite{}, nil) + + jsonData, err := json.Marshal(server) + if err != nil { + log.Fatal(err) + } + + m := flow.New() + handler := transport.New(st, im, mc) + + req, err := http.NewRequest("POST", "/api/v1/server/1/invites", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/server/:id/invites", handler.ServerInvites) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusNotFound { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusNotFound) + } + expected := "[]" + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) +} + +func TestMiddlewares(t *testing.T) { + ctrl := gomock.NewController(t) + st := mock_store.NewMockStorer(ctrl) + im := mock_invite.NewMockInviteManager(ctrl) + mc := mock_minecraft.NewMockMinecraft(ctrl) + + user := store.User{ + Id: "1", + Token: "", + DisplayName: "user", + RefreshToken: "", + TokenExpiry: time.Time{}, + } + + sess := store.Session{ + Token: "foo", + UID: user.Id, + Expiry: time.Time{}, + } + + t.Run("cors middleware", func(t *testing.T) { + m := flow.New() + handler := transport.New(st, im, mc) + m.Use(handler.Cors) + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "application/json") + w.Write([]byte(`{"duck": "quacks"}`)) + }) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + if rr.Header().Get("Access-Control-Allow-Origin") != "*" { + t.Errorf("handler returned unexpected Access-Control-Allow-Origin header: got %v want %v", + rr.Header().Get("Access-Control-Allow-Origin"), "*") + } + }) + + t.Run("auth success with valid session token", func(t *testing.T) { + st.EXPECT().SessionUser(sess.Token).Return(user, nil) + m := flow.New() + handler := transport.New(st, im, mc) + m.Use(handler.SessionAuth) + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + req.AddCookie(&http.Cookie{ + Name: "session", + Value: "foo", + }) + + rr := httptest.NewRecorder() + m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "application/json") + w.Write([]byte(`{"duck": "quacks"}`)) + }) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `{"duck": "quacks"}` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + + t.Run("auth denial with no session cookie", func(t *testing.T) { + m := flow.New() + handler := transport.New(st, im, mc) + m.Use(handler.SessionAuth) + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "application/json") + w.Write([]byte(`{"duck": "quacks"}`)) + }) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusForbidden { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusForbidden) + } + }) + + t.Run("auth denial with invalid session token", func(t *testing.T) { + st.EXPECT().SessionUser(sess.Token).Return(store.User{}, sql.ErrNoRows) + m := flow.New() + handler := transport.New(st, im, mc) + m.Use(handler.SessionAuth) + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + req.AddCookie(&http.Cookie{ + Name: "session", + Value: "foo", + }) + + rr := httptest.NewRecorder() + m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "application/json") + w.Write([]byte(`{"duck": "quacks"}`)) + }) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusForbidden { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusForbidden) + } + }) }