Implement beforeMessageGUID/afterMessageGUID/beforeDate
This commit is contained in:
@@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.severnaya.net/kordophone-mock/v2/data"
|
"code.severnaya.net/kordophone-mock/v2/data"
|
||||||
"code.severnaya.net/kordophone-mock/v2/model"
|
"code.severnaya.net/kordophone-mock/v2/model"
|
||||||
@@ -25,6 +26,14 @@ type Server struct {
|
|||||||
updateItemSeq int
|
updateItemSeq int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MessagesQuery struct {
|
||||||
|
ConversationGUID string
|
||||||
|
BeforeDate *time.Time
|
||||||
|
AfterGUID *string
|
||||||
|
BeforeGUID *string
|
||||||
|
Limit *int
|
||||||
|
}
|
||||||
|
|
||||||
type AuthError struct {
|
type AuthError struct {
|
||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
@@ -136,15 +145,58 @@ func (s *Server) CheckBearerToken(token string) bool {
|
|||||||
return s.authenticateToken(token)
|
return s.authenticateToken(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) MessagesForConversation(conversation *model.Conversation) []model.Message {
|
func (s *Server) PerformMessageQuery(query *MessagesQuery) []model.Message {
|
||||||
messages := s.messageStore[conversation.Guid]
|
messages := s.messageStore[query.ConversationGUID]
|
||||||
|
|
||||||
|
// Sort
|
||||||
sort.Slice(messages, func(i int, j int) bool {
|
sort.Slice(messages, func(i int, j int) bool {
|
||||||
return messages[i].Date.Before(messages[j].Date)
|
return messages[i].Date.Before(messages[j].Date)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Apply before/after filters
|
||||||
|
if query.BeforeGUID != nil {
|
||||||
|
beforeGUID := *query.BeforeGUID
|
||||||
|
for i := range messages {
|
||||||
|
if messages[i].Guid == beforeGUID {
|
||||||
|
messages = messages[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if query.AfterGUID != nil {
|
||||||
|
afterGUID := *query.AfterGUID
|
||||||
|
for i := range messages {
|
||||||
|
if messages[i].Guid == afterGUID {
|
||||||
|
messages = messages[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if query.BeforeDate != nil {
|
||||||
|
beforeDate := *query.BeforeDate
|
||||||
|
for i := range messages {
|
||||||
|
if messages[i].Date.Before(beforeDate) {
|
||||||
|
messages = messages[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit
|
||||||
|
if query.Limit != nil {
|
||||||
|
limit := *query.Limit
|
||||||
|
if len(messages) > limit {
|
||||||
|
messages = messages[:limit]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) MessagesForConversation(conversation *model.Conversation) []model.Message {
|
||||||
|
return s.PerformMessageQuery(&MessagesQuery{
|
||||||
|
ConversationGUID: conversation.Guid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) AppendMessageToConversation(conversation *model.Conversation, message model.Message) {
|
func (s *Server) AppendMessageToConversation(conversation *model.Conversation, message model.Message) {
|
||||||
s.messageStore[conversation.Guid] = append(s.messageStore[conversation.Guid], message)
|
s.messageStore[conversation.Guid] = append(s.messageStore[conversation.Guid], message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ func (m *MockHTTPServer) handleConversations(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockHTTPServer) handleMessages(w http.ResponseWriter, r *http.Request) {
|
func (m *MockHTTPServer) handleMessages(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO handle optional "limit", "beforeDate", "beforeMessageGUID", and "afterMessageGUID" parameters
|
|
||||||
|
|
||||||
guid := r.URL.Query().Get("guid")
|
guid := r.URL.Query().Get("guid")
|
||||||
if len(guid) == 0 {
|
if len(guid) == 0 {
|
||||||
log.Error().Msg("handleMessage: Got empty guid parameter")
|
log.Error().Msg("handleMessage: Got empty guid parameter")
|
||||||
@@ -108,7 +106,43 @@ func (m *MockHTTPServer) handleMessages(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := m.Server.MessagesForConversation(conversation)
|
beforeDate := r.URL.Query().Get("beforeDate")
|
||||||
|
beforeGUID := r.URL.Query().Get("beforeMessageGUID")
|
||||||
|
afterGUID := r.URL.Query().Get("afterMessageGUID")
|
||||||
|
limit := r.URL.Query().Get("limit")
|
||||||
|
|
||||||
|
stringOrNil := func(s string) *string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
dateOrNil := func(s string) *time.Time {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, _ := time.Parse(time.RFC3339, s)
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
intOrNil := func(s string) *int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i, _ := strconv.Atoi(s)
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
query := server.MessagesQuery{
|
||||||
|
Limit: intOrNil(limit),
|
||||||
|
BeforeDate: dateOrNil(beforeDate),
|
||||||
|
BeforeGUID: stringOrNil(beforeGUID),
|
||||||
|
AfterGUID: stringOrNil(afterGUID),
|
||||||
|
ConversationGUID: conversation.Guid,
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := m.Server.PerformMessageQuery(&query)
|
||||||
|
|
||||||
jsonData, err := json.Marshal(messages)
|
jsonData, err := json.Marshal(messages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.severnaya.net/kordophone-mock/v2/data"
|
||||||
"code.severnaya.net/kordophone-mock/v2/model"
|
"code.severnaya.net/kordophone-mock/v2/model"
|
||||||
"code.severnaya.net/kordophone-mock/v2/server"
|
"code.severnaya.net/kordophone-mock/v2/server"
|
||||||
"code.severnaya.net/kordophone-mock/v2/web"
|
"code.severnaya.net/kordophone-mock/v2/web"
|
||||||
@@ -342,3 +343,100 @@ func TestMarkConversation(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected unread count: %d (expected %d)", convo.UnreadCount, 0)
|
t.Fatalf("Unexpected unread count: %d (expected %d)", convo.UnreadCount, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMessageQueries(t *testing.T) {
|
||||||
|
s := web.NewMockHTTPServer(web.MockHTTPServerConfiguration{AuthEnabled: true})
|
||||||
|
httpServer := httptest.NewServer(s)
|
||||||
|
|
||||||
|
// Mock conversation
|
||||||
|
guid := "1234567890"
|
||||||
|
conversation := model.Conversation{
|
||||||
|
Date: time.Now(),
|
||||||
|
Participants: []string{"Alice"},
|
||||||
|
UnreadCount: 0,
|
||||||
|
Guid: guid,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Server.AddConversation(conversation)
|
||||||
|
|
||||||
|
// Mock messages
|
||||||
|
numMessages := 20
|
||||||
|
for i := 0; i < numMessages; i++ {
|
||||||
|
message := data.GenerateRandomMessage(conversation.Participants)
|
||||||
|
s.Server.AppendMessageToConversation(&conversation, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick a pivot message from the sorted list
|
||||||
|
sortedMessages := s.Server.MessagesForConversation(&conversation)
|
||||||
|
pivotMessage := sortedMessages[len(sortedMessages)/2]
|
||||||
|
|
||||||
|
// Query messages before the pivot, test limit also
|
||||||
|
limitMessageCount := 5
|
||||||
|
resp, err := http.Get(httpServer.URL + fmt.Sprintf("/messages?guid=%s&beforeMessageGUID=%s&limit=%d", guid, pivotMessage.Guid, limitMessageCount))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMessageQueries error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode response
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error decoding body: %s", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var messages []model.Message
|
||||||
|
err = json.Unmarshal(body, &messages)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling JSON: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) != limitMessageCount {
|
||||||
|
t.Fatalf("Unexpected num messages: %d (expected %d)", len(messages), limitMessageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure before query is exclusive of the pivot message
|
||||||
|
for _, message := range messages {
|
||||||
|
if message.Guid == pivotMessage.Guid {
|
||||||
|
t.Fatalf("Found pivot guid in before query: %s (expected != %s)", message.Guid, pivotMessage.Guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure messages are actually before the pivot
|
||||||
|
for _, message := range messages {
|
||||||
|
if message.Date.After(pivotMessage.Date) {
|
||||||
|
t.Fatalf("Unexpected message date: %s (expected before %s)", message.Date, pivotMessage.Date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query messages after the pivot
|
||||||
|
resp, err = http.Get(httpServer.URL + fmt.Sprintf("/messages?guid=%s&afterMessageGUID=%s", guid, pivotMessage.Guid))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMessageQueries error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode response
|
||||||
|
body, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error decoding body: %s", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = []model.Message{}
|
||||||
|
err = json.Unmarshal(body, &messages)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling JSON: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure after query is exclusive of the pivot message
|
||||||
|
for _, message := range messages {
|
||||||
|
if message.Guid == pivotMessage.Guid {
|
||||||
|
t.Fatalf("Found pivot guid in after query: %s (expected != %s)", message.Guid, pivotMessage.Guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure messages are actually after the pivot
|
||||||
|
for _, message := range messages {
|
||||||
|
if message.Date.Before(pivotMessage.Date) {
|
||||||
|
t.Fatalf("Unexpected message date: %s (expected after %s)", message.Date, pivotMessage.Date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user