messages: Implements /messages API
This commit is contained in:
@@ -241,7 +241,7 @@ func GenerateRandomName() string {
|
||||
return names[rand.Intn(len(names))]
|
||||
}
|
||||
|
||||
func GenerateRandomMessage() string {
|
||||
func GenerateRandomMessageBody() string {
|
||||
// Generated by GPT-4
|
||||
messages := []string{
|
||||
"Good morning! How are you?",
|
||||
@@ -329,12 +329,29 @@ func GenerateRandomMessage() string {
|
||||
|
||||
func GenerateRandomConversation() model.Conversation {
|
||||
conversation := model.Conversation{
|
||||
Participants: []string{GenerateRandomName()},
|
||||
UnreadCount: 0,
|
||||
LastMessagePreview: GenerateRandomMessage(),
|
||||
Guid: uuid.New().String(),
|
||||
Date: time.Now().Add(-1 * time.Duration(rand.Intn(1000000)) * time.Second),
|
||||
Participants: []string{GenerateRandomName()},
|
||||
UnreadCount: 0,
|
||||
Guid: uuid.New().String(),
|
||||
Date: time.Now().Add(-1 * time.Duration(rand.Intn(1000000)) * time.Second),
|
||||
}
|
||||
|
||||
return conversation
|
||||
}
|
||||
|
||||
func GenerateRandomMessage(participants []string) model.Message {
|
||||
var sender *string = nil
|
||||
if len(participants) == 1 {
|
||||
if rand.Intn(2) == 0 {
|
||||
sender = &participants[0]
|
||||
}
|
||||
} else {
|
||||
sender = &participants[rand.Intn(len(participants))]
|
||||
}
|
||||
|
||||
return model.Message{
|
||||
Text: GenerateRandomMessageBody(),
|
||||
Guid: uuid.New().String(),
|
||||
Date: time.Now().Add(-1 * time.Duration(rand.Intn(1000000)) * time.Second),
|
||||
Sender: sender,
|
||||
}
|
||||
}
|
||||
|
||||
1
main.go
1
main.go
@@ -22,6 +22,7 @@ func main() {
|
||||
|
||||
// Populate with test data
|
||||
s.Server.PopulateWithTestData()
|
||||
log.Printf("Generated test data. %d conversations", len(s.Server.Conversations()))
|
||||
|
||||
log.Fatal(httpServer.ListenAndServe())
|
||||
}
|
||||
|
||||
18
model/message.go
Normal file
18
model/message.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Message struct {
|
||||
Text string `json:"text"`
|
||||
Guid string `json:"guid"`
|
||||
Sender *string `json:"sender"` // Optional: nil means from "me"
|
||||
Date time.Time `json:"date"`
|
||||
|
||||
// Map of attachment GUID to attachment metadata
|
||||
Attachments *map[string]AttributionInfo `json:"attachmentMetadata"` // Optional
|
||||
}
|
||||
|
||||
type AttributionInfo struct {
|
||||
ThumbnailWidth int `json:"pgensw"`
|
||||
ThumbnailHeight int `json:"pgensh"`
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"code.severnaya.net/kordophone-mock/v2/data"
|
||||
"code.severnaya.net/kordophone-mock/v2/model"
|
||||
)
|
||||
@@ -16,6 +18,7 @@ type Server struct {
|
||||
version string
|
||||
conversations []model.Conversation
|
||||
authTokens []model.AuthToken
|
||||
messageStore map[string][]model.Message
|
||||
}
|
||||
|
||||
type AuthError struct {
|
||||
@@ -26,10 +29,20 @@ func (e *AuthError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type DatabaseError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *DatabaseError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
version: VERSION,
|
||||
conversations: []model.Conversation{},
|
||||
authTokens: []model.AuthToken{},
|
||||
messageStore: make(map[string][]model.Message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +54,22 @@ func (s *Server) Conversations() []model.Conversation {
|
||||
return s.conversations
|
||||
}
|
||||
|
||||
func (s *Server) ConversationForGUID(guid string) (*model.Conversation, error) {
|
||||
var conversation *model.Conversation = nil
|
||||
for _, c := range s.conversations {
|
||||
if c.Guid == guid {
|
||||
conversation = &c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if conversation != nil {
|
||||
return conversation, nil
|
||||
}
|
||||
|
||||
return nil, &DatabaseError{message: "Conversation not found"}
|
||||
}
|
||||
|
||||
func (s *Server) AddConversation(c model.Conversation) {
|
||||
s.conversations = append(s.conversations, c)
|
||||
}
|
||||
@@ -50,6 +79,21 @@ func (s *Server) PopulateWithTestData() {
|
||||
cs := make([]model.Conversation, numConversations)
|
||||
for i := 0; i < numConversations; i++ {
|
||||
cs[i] = data.GenerateRandomConversation()
|
||||
|
||||
// Generate messages
|
||||
convo := &cs[i]
|
||||
var lastMessage model.Message
|
||||
for i := 0; i < 100; i++ {
|
||||
message := data.GenerateRandomMessage(convo.Participants)
|
||||
s.AppendMessageToConversation(convo, message)
|
||||
|
||||
if lastMessage.Date.Before(message.Date) {
|
||||
lastMessage = message
|
||||
}
|
||||
}
|
||||
|
||||
// Update last message preview
|
||||
convo.LastMessagePreview = lastMessage.Text
|
||||
}
|
||||
|
||||
s.conversations = cs
|
||||
@@ -75,6 +119,19 @@ func (s *Server) CheckBearerToken(token string) bool {
|
||||
return s.authenticateToken(token)
|
||||
}
|
||||
|
||||
func (s *Server) MessagesForConversation(conversation model.Conversation) []model.Message {
|
||||
messages := s.messageStore[conversation.Guid]
|
||||
sort.Slice(messages, func(i int, j int) bool {
|
||||
return messages[i].Date.Before(messages[j].Date)
|
||||
})
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func (s *Server) AppendMessageToConversation(conversation *model.Conversation, message model.Message) {
|
||||
s.messageStore[conversation.Guid] = append(s.messageStore[conversation.Guid], message)
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
func (s *Server) registerAuthToken(token *model.AuthToken) {
|
||||
|
||||
@@ -82,6 +82,35 @@ func (m *MockHTTPServer) handleConversations(w http.ResponseWriter, r *http.Requ
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
func (m *MockHTTPServer) handleMessages(w http.ResponseWriter, r *http.Request) {
|
||||
guid := r.URL.Query().Get("guid")
|
||||
if len(guid) == 0 {
|
||||
log.Println("handleMessage: Got empty guid parameter")
|
||||
http.Error(w, "no guid parameter specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
conversation, err := m.Server.ConversationForGUID(guid)
|
||||
if err != nil {
|
||||
log.Printf("handleMessage: Error getting conversation (%s): %s", guid, err)
|
||||
http.Error(w, "conversation not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
messages := m.Server.MessagesForConversation(*conversation)
|
||||
|
||||
jsonData, err := json.Marshal(messages)
|
||||
if err != nil {
|
||||
log.Printf("Error marshalling messages: %s", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Write JSON to response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
func (m *MockHTTPServer) handleAuthenticate(w http.ResponseWriter, r *http.Request) {
|
||||
// Decode request body as AuthenticationRequest
|
||||
var authReq AuthenticationRequest
|
||||
@@ -130,6 +159,7 @@ func NewMockHTTPServer(config MockHTTPServerConfiguration) *MockHTTPServer {
|
||||
this.mux.Handle("/conversations", http.HandlerFunc(this.handleConversations))
|
||||
this.mux.Handle("/status", http.HandlerFunc(this.handleStatus))
|
||||
this.mux.Handle("/authenticate", http.HandlerFunc(this.handleAuthenticate))
|
||||
this.mux.Handle("/messages", http.HandlerFunc(this.handleMessages))
|
||||
|
||||
return &this
|
||||
}
|
||||
|
||||
@@ -107,6 +107,60 @@ func TestConversations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessages(t *testing.T) {
|
||||
server := web.NewMockHTTPServer(web.MockHTTPServerConfiguration{})
|
||||
httpServer := httptest.NewServer(server)
|
||||
|
||||
const sender = "Alice"
|
||||
const text = "This is a test."
|
||||
|
||||
conversation := model.Conversation{
|
||||
Date: time.Now(),
|
||||
Participants: []string{sender},
|
||||
UnreadCount: 1,
|
||||
Guid: "1234567890",
|
||||
}
|
||||
|
||||
server.Server.AddConversation(conversation)
|
||||
|
||||
message := model.Message{
|
||||
Text: text,
|
||||
Sender: &conversation.Participants[0],
|
||||
Date: time.Now(),
|
||||
}
|
||||
|
||||
server.Server.AppendMessageToConversation(&conversation, message)
|
||||
|
||||
resp, err := http.Get(httpServer.URL + "/messages?guid=" + *&conversation.Guid)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMessages error: %s", err)
|
||||
}
|
||||
|
||||
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) != 1 {
|
||||
t.Fatalf("Unexpected num messages: %d (expected %d)", len(messages), 1)
|
||||
}
|
||||
|
||||
fetchedMessage := messages[0]
|
||||
if fetchedMessage.Text != message.Text {
|
||||
t.Fatalf("Unexpected message text: %s (expected %s)", fetchedMessage.Text, message.Text)
|
||||
}
|
||||
|
||||
if *fetchedMessage.Sender != *message.Sender {
|
||||
t.Fatalf("Unexpected message sender: %s (expected %s)", *fetchedMessage.Sender, *message.Sender)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthentication(t *testing.T) {
|
||||
s := web.NewMockHTTPServer(web.MockHTTPServerConfiguration{AuthEnabled: true})
|
||||
httpServer := httptest.NewServer(s)
|
||||
|
||||
Reference in New Issue
Block a user