HTTP API: 404 for a chat id the list of chats leaves out (closes #5)
check / check (push) Successful in 1m19s
check / check (push) Successful in 1m19s
The chat client keeps contact records that GET /api/v1/chats does not list, such as the bot's own profile (1 on a new profile) and a contact it creates itself (2), and reads or sends in them when asked. The message endpoints now look the id up, through chatID in internal/api/chats.go, in the same list the chats endpoint answers with, and answer 404 for any id not in it. The webhook endpoints can call chatID too. Model: opus-5-5
This commit is contained in:
@@ -17,6 +17,12 @@ const (
|
||||
notJSON = `{"error":"the body must be JSON such as {\"text\":\"hello\"}"}` + "\n"
|
||||
)
|
||||
|
||||
// oneChat returns the bot's contacts in these tests: one, whose chat is
|
||||
// the one messagesPath names.
|
||||
func oneChat() []simplex.Contact {
|
||||
return []simplex.Contact{{ContactID: 3, Status: "active"}}
|
||||
}
|
||||
|
||||
// chatItem is a chat item decoded from a record shaped as the chat
|
||||
// client sends it, reduced to the fields the API reads.
|
||||
func chatItem(t *testing.T, record string) simplex.ChatItem {
|
||||
@@ -54,7 +60,7 @@ func post(
|
||||
func TestMessages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeClient{items: []simplex.ChatItem{
|
||||
client := &fakeClient{contacts: oneChat(), items: []simplex.ChatItem{
|
||||
chatItem(t, `{"meta":{"itemId":7,"itemTs":"2026-09-29T03:13:46.521359978Z"},
|
||||
"content":{"type":"rcvChatFeature","feature":"calls"}}`),
|
||||
chatItem(t, `{"meta":{"itemId":9,"itemTs":"2026-09-29T03:14:34Z"},
|
||||
@@ -91,7 +97,7 @@ func TestMessages(t *testing.T) {
|
||||
func TestNoMessages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rec := request(t, newAPI(credential, &fakeClient{}),
|
||||
rec := request(t, newAPI(credential, &fakeClient{contacts: oneChat()}),
|
||||
http.MethodGet, messagesPath, bearer)
|
||||
|
||||
want := `{"messages":[]}` + "\n"
|
||||
@@ -101,7 +107,7 @@ func TestNoMessages(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestMessagesCount: count is a whole number from 1 to 100, and 20 when
|
||||
// absent. Anything else is refused before the chat client is asked.
|
||||
// absent. Anything else is refused before the chat's items are read.
|
||||
func TestMessagesCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -122,7 +128,7 @@ func TestMessagesCount(t *testing.T) {
|
||||
t.Run(query, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeClient{}
|
||||
client := &fakeClient{contacts: oneChat()}
|
||||
rec := request(t, newAPI(credential, client),
|
||||
http.MethodGet, messagesPath+query, bearer)
|
||||
|
||||
@@ -145,14 +151,18 @@ func TestMessagesCount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoSuchChat: a chat id that is not a positive whole number gets 404
|
||||
// without asking the chat client, and so does one it has no contact
|
||||
// for, whether reading messages or sending one.
|
||||
// TestNoSuchChat: an id that GET /api/v1/chats does not list gets 404,
|
||||
// whether reading messages or sending one, and nothing is read or sent.
|
||||
// That includes 1 and 2, contact records the chat client keeps on a new
|
||||
// profile and would read or send in. A contact the chat client no
|
||||
// longer has when asked also gets 404.
|
||||
func TestNoSuchChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, id := range []string{"tester", "0", "-3", "3.5", "99999999999999999999"} {
|
||||
client := &fakeClient{}
|
||||
for _, id := range []string{
|
||||
"1", "2", "4", "tester", "0", "-3", "3.5", "99999999999999999999",
|
||||
} {
|
||||
client := &fakeClient{contacts: oneChat()}
|
||||
srv := newAPI(credential, client)
|
||||
path := "/api/v1/chats/" + id + "/messages"
|
||||
|
||||
@@ -167,11 +177,11 @@ func TestNoSuchChat(t *testing.T) {
|
||||
}
|
||||
|
||||
if client.contactID != 0 {
|
||||
t.Errorf("chat %q: the chat client was asked", id)
|
||||
t.Errorf("chat %q: the chat was read or sent to", id)
|
||||
}
|
||||
}
|
||||
|
||||
srv := newAPI(credential, &fakeClient{err: simplex.ErrNoContact})
|
||||
srv := newAPI(credential, &fakeClient{contacts: oneChat(), err: simplex.ErrNoContact})
|
||||
|
||||
for _, rec := range []*httptest.ResponseRecorder{
|
||||
request(t, srv, http.MethodGet, messagesPath, bearer),
|
||||
@@ -184,12 +194,36 @@ func TestNoSuchChat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatLookupFailure: when the chats cannot be read to look the id
|
||||
// up, the answer is 500 and nothing is read or sent.
|
||||
func TestChatLookupFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeClient{contactsErr: errChat}
|
||||
srv := newAPI(credential, client)
|
||||
|
||||
want := `{"error":"the chats could not be read"}` + "\n"
|
||||
|
||||
for _, rec := range []*httptest.ResponseRecorder{
|
||||
request(t, srv, http.MethodGet, messagesPath, bearer),
|
||||
post(t, srv, messagesPath, `{"text":"hello"}`),
|
||||
} {
|
||||
if rec.Code != http.StatusInternalServerError || rec.Body.String() != want {
|
||||
t.Errorf("response = %d %q, want 500 %q", rec.Code, rec.Body.String(), want)
|
||||
}
|
||||
}
|
||||
|
||||
if client.contactID != 0 {
|
||||
t.Error("the chat was read or sent to")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMessagesFailure: when the chat client fails, the answer says so in
|
||||
// a chosen sentence, never in the error's own text.
|
||||
func TestMessagesFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rec := request(t, newAPI(credential, &fakeClient{err: errChat}),
|
||||
rec := request(t, newAPI(credential, &fakeClient{contacts: oneChat(), err: errChat}),
|
||||
http.MethodGet, messagesPath, bearer)
|
||||
|
||||
want := `{"error":"the messages could not be read"}` + "\n"
|
||||
@@ -203,7 +237,7 @@ func TestMessagesFailure(t *testing.T) {
|
||||
func TestSend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeClient{sent: chatItem(t, `{"meta":{"itemId":12,
|
||||
client := &fakeClient{contacts: oneChat(), sent: chatItem(t, `{"meta":{"itemId":12,
|
||||
"itemTs":"2026-09-29T03:14:43.519552587Z"},"content":{"type":"sndMsgContent",
|
||||
"msgContent":{"type":"text","text":"hello"}}}`)}
|
||||
|
||||
@@ -250,7 +284,7 @@ func TestSendBadBody(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeClient{}
|
||||
client := &fakeClient{contacts: oneChat()}
|
||||
rec := post(t, newAPI(credential, client), messagesPath, tc.body)
|
||||
|
||||
if rec.Code != tc.status || rec.Body.String() != tc.answer {
|
||||
@@ -277,19 +311,19 @@ func TestSendRefused(t *testing.T) {
|
||||
answer string
|
||||
}{
|
||||
"contact deleted": {
|
||||
&fakeClient{err: simplex.ErrContactNotReady},
|
||||
&fakeClient{contacts: oneChat(), err: simplex.ErrContactNotReady},
|
||||
http.StatusConflict, `{"error":"the contact cannot receive messages"}`,
|
||||
},
|
||||
"text too long": {
|
||||
&fakeClient{err: simplex.ErrMessageTooLarge},
|
||||
&fakeClient{contacts: oneChat(), err: simplex.ErrMessageTooLarge},
|
||||
http.StatusRequestEntityTooLarge, `{"error":"the text is too long"}`,
|
||||
},
|
||||
"anything else": {
|
||||
&fakeClient{err: errChat},
|
||||
&fakeClient{contacts: oneChat(), err: errChat},
|
||||
http.StatusInternalServerError, `{"error":"the message could not be sent"}`,
|
||||
},
|
||||
"no message in the answer": {
|
||||
&fakeClient{sent: chatItem(t, `{"meta":{"itemId":13},
|
||||
&fakeClient{contacts: oneChat(), sent: chatItem(t, `{"meta":{"itemId":13},
|
||||
"content":{"type":"sndDirectEvent"}}`)},
|
||||
http.StatusInternalServerError,
|
||||
`{"error":"the chat client's answer could not be read"}`,
|
||||
|
||||
Reference in New Issue
Block a user