From d3553d5955b07ca1a3b43c0502bed8815cd3b4fc Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Thu, 22 Sep 2022 22:40:46 +0930 Subject: [PATCH] Add tests for friend list --- Backend/Api/Friends/CreateFriendRequest.go | 87 ++++++++++++++ Backend/Api/Friends/EncryptedFriendsList.go | 41 ------- Backend/Api/Friends/Friends.go | 73 +++--------- Backend/Api/Friends/Friends_test.go | 124 ++++++++++++++++++++ Backend/Api/Messages/Conversations.go | 6 +- Backend/Api/Messages/Conversations_test.go | 1 + Backend/Api/Messages/MessageThread.go | 13 +- Backend/Api/Messages/MessageThread_test.go | 88 ++++++++++++++ Backend/Api/Users/SearchUsers.go | 1 - Backend/Api/Users/SearchUsers_test.go | 106 +++++++++++++++++ Backend/Database/FriendRequests.go | 8 +- Backend/Database/Messages.go | 9 +- Backend/Models/Friends.go | 2 + Backend/Tests/Init.go | 33 +++--- 14 files changed, 471 insertions(+), 121 deletions(-) create mode 100644 Backend/Api/Friends/CreateFriendRequest.go delete mode 100644 Backend/Api/Friends/EncryptedFriendsList.go create mode 100644 Backend/Api/Friends/Friends_test.go create mode 100644 Backend/Api/Users/SearchUsers_test.go diff --git a/Backend/Api/Friends/CreateFriendRequest.go b/Backend/Api/Friends/CreateFriendRequest.go new file mode 100644 index 0000000..d7f0b53 --- /dev/null +++ b/Backend/Api/Friends/CreateFriendRequest.go @@ -0,0 +1,87 @@ +package Friends + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" +) + +// CreateFriendRequest creates a FriendRequest from post data +func CreateFriendRequest(w http.ResponseWriter, r *http.Request) { + var ( + friendRequest Models.FriendRequest + requestBody []byte + returnJSON []byte + err error + ) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + err = json.Unmarshal(requestBody, &friendRequest) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + friendRequest.AcceptedAt.Scan(nil) + + err = Database.CreateFriendRequest(&friendRequest) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + returnJSON, err = json.MarshalIndent(friendRequest, "", " ") + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJSON) +} + +// CreateFriendRequestQrCode creates a FriendRequest from post data from qr code scan +func CreateFriendRequestQrCode(w http.ResponseWriter, r *http.Request) { + var ( + friendRequests []Models.FriendRequest + requestBody []byte + i int + err error + ) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + err = json.Unmarshal(requestBody, &friendRequests) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + for i = range friendRequests { + friendRequests[i].AcceptedAt.Time = time.Now() + friendRequests[i].AcceptedAt.Valid = true + } + + err = Database.CreateFriendRequests(&friendRequests) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) +} diff --git a/Backend/Api/Friends/EncryptedFriendsList.go b/Backend/Api/Friends/EncryptedFriendsList.go deleted file mode 100644 index 79d6113..0000000 --- a/Backend/Api/Friends/EncryptedFriendsList.go +++ /dev/null @@ -1,41 +0,0 @@ -package Friends - -import ( - "encoding/json" - "net/http" - - "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" - "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" - "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" -) - -// FriendRequestList gets friend request list -func FriendRequestList(w http.ResponseWriter, r *http.Request) { - var ( - userSession Models.Session - friends []Models.FriendRequest - returnJSON []byte - err error - ) - - userSession, err = Auth.CheckCookie(r) - if err != nil { - http.Error(w, "Forbidden", http.StatusUnauthorized) - return - } - - friends, err = Database.GetFriendRequestsByUserID(userSession.UserID.String()) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - returnJSON, err = json.MarshalIndent(friends, "", " ") - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(returnJSON) -} diff --git a/Backend/Api/Friends/Friends.go b/Backend/Api/Friends/Friends.go index d7f0b53..3bd58ba 100644 --- a/Backend/Api/Friends/Friends.go +++ b/Backend/Api/Friends/Friends.go @@ -2,86 +2,47 @@ package Friends import ( "encoding/json" - "io/ioutil" "net/http" - "time" + "net/url" + "strconv" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) -// CreateFriendRequest creates a FriendRequest from post data -func CreateFriendRequest(w http.ResponseWriter, r *http.Request) { +// FriendRequestList gets friend request list +func FriendRequestList(w http.ResponseWriter, r *http.Request) { var ( - friendRequest Models.FriendRequest - requestBody []byte - returnJSON []byte - err error + userSession Models.Session + friends []Models.FriendRequest + values url.Values + returnJSON []byte + page int + err error ) - requestBody, err = ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } + values = r.URL.Query() - err = json.Unmarshal(requestBody, &friendRequest) + page, err = strconv.Atoi(values.Get("page")) if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return + page = 0 } - friendRequest.AcceptedAt.Scan(nil) + userSession, _ = Auth.CheckCookie(r) - err = Database.CreateFriendRequest(&friendRequest) + friends, err = Database.GetFriendRequestsByUserID(userSession.UserID.String(), page) if err != nil { http.Error(w, "Error", http.StatusInternalServerError) return } - returnJSON, err = json.MarshalIndent(friendRequest, "", " ") + returnJSON, err = json.MarshalIndent(friends, "", " ") if err != nil { http.Error(w, "Error", http.StatusInternalServerError) return } - // Return updated json w.WriteHeader(http.StatusOK) w.Write(returnJSON) } - -// CreateFriendRequestQrCode creates a FriendRequest from post data from qr code scan -func CreateFriendRequestQrCode(w http.ResponseWriter, r *http.Request) { - var ( - friendRequests []Models.FriendRequest - requestBody []byte - i int - err error - ) - - requestBody, err = ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - err = json.Unmarshal(requestBody, &friendRequests) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - for i = range friendRequests { - friendRequests[i].AcceptedAt.Time = time.Now() - friendRequests[i].AcceptedAt.Valid = true - } - - err = Database.CreateFriendRequests(&friendRequests) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - // Return updated json - w.WriteHeader(http.StatusOK) -} diff --git a/Backend/Api/Friends/Friends_test.go b/Backend/Api/Friends/Friends_test.go new file mode 100644 index 0000000..d18de12 --- /dev/null +++ b/Backend/Api/Friends/Friends_test.go @@ -0,0 +1,124 @@ +package Friends_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_FriendRequestList(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + for i := 0; i < 30; i++ { + u2, err := Tests.InitTestCreateUser(fmt.Sprintf("test%d", i)) + + decodedPublicKey := Seeder.GetPubKey() + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + encPublicKey, err := key.AesEncrypt([]byte(Seeder.PublicKey)) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + friendReq := Models.FriendRequest{ + UserID: u.ID, + FriendID: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey( + []byte(u2.ID.String()), + decodedPublicKey, + ), + ), + FriendUsername: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey( + []byte(u2.Username), + decodedPublicKey, + ), + ), + FriendPublicAsymmetricKey: base64.StdEncoding.EncodeToString( + encPublicKey, + ), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, decodedPublicKey), + ), + } + + if i > 20 { + friendReq.AcceptedAt.Time = time.Now() + friendReq.AcceptedAt.Valid = true + } + + err = Database.CreateFriendRequest(&friendReq) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + } + + req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/friend_requests", nil) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var users []Models.FriendRequest + + json.Unmarshal(requestBody, &users) + + if len(users) != 20 { + t.Errorf("Expected %d, recieved %d", 1, len(users)) + return + } + + for i := 0; i < 20; i++ { + eq := true + if i > 8 { + eq = false + } + if users[i].AcceptedAt.Valid != eq { + t.Errorf( + "Expected %v, recieved %v, on user %d", + eq, users[i].AcceptedAt.Valid, + i, + ) + return + } + } +} diff --git a/Backend/Api/Messages/Conversations.go b/Backend/Api/Messages/Conversations.go index e4c6e81..1639111 100644 --- a/Backend/Api/Messages/Conversations.go +++ b/Backend/Api/Messages/Conversations.go @@ -30,11 +30,7 @@ func ConversationList(w http.ResponseWriter, r *http.Request) { page = 0 } - userSession, err = Auth.CheckCookie(r) - if err != nil { - http.Error(w, "Forbidden", http.StatusUnauthorized) - return - } + userSession, _ = Auth.CheckCookie(r) conversationDetails, err = Database.GetUserConversationsByUserId( userSession.UserID.String(), diff --git a/Backend/Api/Messages/Conversations_test.go b/Backend/Api/Messages/Conversations_test.go index 21163ce..49f3654 100644 --- a/Backend/Api/Messages/Conversations_test.go +++ b/Backend/Api/Messages/Conversations_test.go @@ -102,6 +102,7 @@ func Test_ConversationsList(t *testing.T) { if len(conversations) != 1 { t.Errorf("Expected %d, recieved %d", 1, len(conversations)) + return } conv := conversations[0] diff --git a/Backend/Api/Messages/MessageThread.go b/Backend/Api/Messages/MessageThread.go index ff466d3..1135c20 100644 --- a/Backend/Api/Messages/MessageThread.go +++ b/Backend/Api/Messages/MessageThread.go @@ -3,6 +3,8 @@ package Messages import ( "encoding/json" "net/http" + "net/url" + "strconv" "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" @@ -17,7 +19,9 @@ func Messages(w http.ResponseWriter, r *http.Request) { message Models.Message urlVars map[string]string associationKey string + values url.Values returnJSON []byte + page int i int ok bool err error @@ -30,7 +34,14 @@ func Messages(w http.ResponseWriter, r *http.Request) { return } - messages, err = Database.GetMessagesByAssociationKey(associationKey) + values = r.URL.Query() + + page, err = strconv.Atoi(values.Get("page")) + if err != nil { + page = 0 + } + + messages, err = Database.GetMessagesByAssociationKey(associationKey, page) if !ok { http.Error(w, "Not Found", http.StatusNotFound) return diff --git a/Backend/Api/Messages/MessageThread_test.go b/Backend/Api/Messages/MessageThread_test.go index 3d3dada..bf4325e 100644 --- a/Backend/Api/Messages/MessageThread_test.go +++ b/Backend/Api/Messages/MessageThread_test.go @@ -115,3 +115,91 @@ func Test_Messages(t *testing.T) { t.Errorf("Expected %s, recieved %s", "Test converation", string(decrypedData)) } } + +func Test_MessagesPagination(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + userKey, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + dataCiphertext, err := key.AesEncrypt([]byte("Test message")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + senderIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + keyCiphertext, err := userKey.AesEncrypt( + []byte(base64.StdEncoding.EncodeToString(key.Key)), + ) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + for i := 0; i < 50; i++ { + message := Models.Message{ + MessageData: Models.MessageData{ + Data: base64.StdEncoding.EncodeToString(dataCiphertext), + SenderID: base64.StdEncoding.EncodeToString(senderIDCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString(keyCiphertext), + }, + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + AssociationKey: "AssociationKey", + } + + err = Database.CreateMessage(&message) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + } + + resp, err := client.Get(ts.URL + "/api/v1/auth/messages/AssociationKey") + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + var m []Models.Message + err = json.Unmarshal(requestBody, &m) + + if len(m) != 20 { + t.Errorf("Expected %d, recieved %d", 20, len(m)) + } +} diff --git a/Backend/Api/Users/SearchUsers.go b/Backend/Api/Users/SearchUsers.go index 51f2e62..a073749 100644 --- a/Backend/Api/Users/SearchUsers.go +++ b/Backend/Api/Users/SearchUsers.go @@ -46,7 +46,6 @@ func SearchUsers(w http.ResponseWriter, r *http.Request) { returnJSON, err = json.MarshalIndent(user, "", " ") if err != nil { - panic(err) http.Error(w, "Not Found", http.StatusNotFound) return } diff --git a/Backend/Api/Users/SearchUsers_test.go b/Backend/Api/Users/SearchUsers_test.go new file mode 100644 index 0000000..1b018f3 --- /dev/null +++ b/Backend/Api/Users/SearchUsers_test.go @@ -0,0 +1,106 @@ +package Users_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_SearchUsers(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u2, err := Tests.InitTestCreateUser("abcd") + + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/api/v1/auth/users?username=%s", ts.URL, u2.Username), + nil, + ) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var user Models.User + + json.Unmarshal(requestBody, &user) + + if user.Username != "abcd" { + t.Errorf("Expected abcd, recieved %s", user.Username) + return + } + + if user.Password != "" { + t.Errorf("Expected \"\", recieved %s", user.Password) + return + } + + if user.AsymmetricPrivateKey != "" { + t.Errorf("Expected \"\", recieved %s", user.AsymmetricPrivateKey) + return + } +} + +func Test_SearchUsersPartialMatchFails(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + _, err = Tests.InitTestCreateUser("abcd") + + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/api/v1/auth/users?username=%s", ts.URL, "abc"), + nil, + ) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNotFound { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var user interface{} + + json.Unmarshal(requestBody, &user) + + if user != nil { + t.Errorf("Expected nil, recieved %+v", user) + return + } +} diff --git a/Backend/Database/FriendRequests.go b/Backend/Database/FriendRequests.go index d93c9e7..951a7a1 100644 --- a/Backend/Database/FriendRequests.go +++ b/Backend/Database/FriendRequests.go @@ -22,14 +22,20 @@ func GetFriendRequestByID(id string) (Models.FriendRequest, error) { } // GetFriendRequestsByUserID gets friend request by user id -func GetFriendRequestsByUserID(userID string) ([]Models.FriendRequest, error) { +func GetFriendRequestsByUserID(userID string, page int) ([]Models.FriendRequest, error) { var ( friends []Models.FriendRequest + offset int err error ) + offset = page * PageSize + err = DB.Model(Models.FriendRequest{}). Where("user_id = ?", userID). + Offset(offset). + Limit(PageSize). + Order("created_at DESC"). Find(&friends). Error diff --git a/Backend/Database/Messages.go b/Backend/Database/Messages.go index dd0fbfe..37d0c14 100644 --- a/Backend/Database/Messages.go +++ b/Backend/Database/Messages.go @@ -22,15 +22,20 @@ func GetMessageByID(id string) (Models.Message, error) { } // GetMessagesByAssociationKey for getting whole thread -// TODO: Add pagination -func GetMessagesByAssociationKey(associationKey string) ([]Models.Message, error) { +func GetMessagesByAssociationKey(associationKey string, page int) ([]Models.Message, error) { var ( messages []Models.Message + offset int err error ) + offset = page * PageSize + err = DB.Preload("MessageData"). Preload("MessageData.Attachment"). + Offset(offset). + Limit(PageSize). + Order("created_at DESC"). Find(&messages, "association_key = ?", associationKey). Error diff --git a/Backend/Models/Friends.go b/Backend/Models/Friends.go index 9dc892d..6438d97 100644 --- a/Backend/Models/Friends.go +++ b/Backend/Models/Friends.go @@ -2,6 +2,7 @@ package Models import ( "database/sql" + "time" "github.com/gofrs/uuid" ) @@ -17,4 +18,5 @@ type FriendRequest struct { FriendPublicAsymmetricKey string ` json:"asymmetric_public_key"` // Stored encrypted SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted AcceptedAt sql.NullTime ` json:"accepted_at"` + CreatedAt time.Time `gorm:"not null" json:"created_at"` } diff --git a/Backend/Tests/Init.go b/Backend/Tests/Init.go index fdb4b48..dde5c4f 100644 --- a/Backend/Tests/Init.go +++ b/Backend/Tests/Init.go @@ -19,28 +19,17 @@ import ( "github.com/gorilla/mux" ) -// InitTestEnv initializes the test environment -// client is used for making authenticated requests -// ts is the testing server -// err, in case it fails ¯\_(ツ)_/¯ -func InitTestEnv() (*http.Client, *httptest.Server, error) { - log.SetOutput(ioutil.Discard) - Database.InitTest() - - r := mux.NewRouter() - Api.InitAPIEndpoints(r) - ts := httptest.NewServer(r) - +func InitTestCreateUser(username string) (Models.User, error) { userKey, err := Seeder.GenerateAesKey() if err != nil { - return http.DefaultClient, ts, err + return Models.User{}, err } pubKey := Seeder.GetPubKey() p, _ := Auth.HashPassword("password") u := Models.User{ - Username: "test", + Username: username, Password: p, AsymmetricPublicKey: Seeder.PublicKey, AsymmetricPrivateKey: Seeder.EncryptedPrivateKey, @@ -50,6 +39,22 @@ func InitTestEnv() (*http.Client, *httptest.Server, error) { } err = Database.CreateUser(&u) + return u, err +} + +// InitTestEnv initializes the test environment +// client is used for making authenticated requests +// ts is the testing server +// err, in case it fails ¯\_(ツ)_/¯ +func InitTestEnv() (*http.Client, *httptest.Server, error) { + log.SetOutput(ioutil.Discard) + Database.InitTest() + + r := mux.NewRouter() + Api.InitAPIEndpoints(r) + ts := httptest.NewServer(r) + + u, err := InitTestCreateUser("test") if err != nil { return http.DefaultClient, ts, err }