HTTP API: a chat's recent messages, and sending a message (closes #5)
check / check (push) Successful in 1m5s

`GET /api/v1/chats/{id}/messages?count=N` returns a chat's recent messages, oldest first (`count` 1 to 100, default 20), and `POST` to the same path sends a text message and returns it once the chat client has taken it. Both answer `404` for any id the chats list does not show, looked up in the same contact list. The message record is defined once, for the webhooks to reuse.

Disclosures: items are read five at a time, because one item can be many times its text and 100 at once could pass the 16 MiB read limit; text too long gets `413`, a contact who deleted the chat `409` (unverified for one still connecting); a query the server cannot read gets its own `400`.

Model: opus-5-5
This commit was merged in pull request #15.
This commit is contained in:
2026-09-29 07:33:36 +02:00
parent 8f0906d677
commit f53b666119
12 changed files with 1143 additions and 53 deletions
+36 -7
View File
@@ -22,19 +22,26 @@ const (
bearer = "Bearer " + credential
chatsPath = "/api/v1/chats"
messagesPath = "/api/v1/chats/3/messages"
unauthorized = `{"error":"unauthorized"}` + "\n"
)
var errChat = errors.New("sqlite: database is locked at /var/lib/simplexcalc")
// fakeClient stands in for the chat client. It answers with contacts,
// or with err, and remembers what it was asked.
// items or sent, or with an error: contactsErr from Contacts, err from
// the others. It remembers what it was asked.
type fakeClient struct {
contacts []simplex.Contact
err error
contacts []simplex.Contact
items []simplex.ChatItem
sent simplex.ChatItem
contactsErr error
err error
userID int64
hadDeadline bool
userID, contactID int64
count int
text string
hadDeadline bool
}
func (f *fakeClient) Contacts(
@@ -43,7 +50,25 @@ func (f *fakeClient) Contacts(
f.userID = userID
_, f.hadDeadline = ctx.Deadline()
return f.contacts, f.err
return f.contacts, f.contactsErr
}
func (f *fakeClient) ChatItems(
ctx context.Context, contactID int64, count int,
) ([]simplex.ChatItem, error) {
f.contactID, f.count = contactID, count
_, f.hadDeadline = ctx.Deadline()
return f.items, f.err
}
func (f *fakeClient) SendMessage(
ctx context.Context, contactID int64, text string,
) (simplex.ChatItem, error) {
f.contactID, f.text = contactID, text
_, f.hadDeadline = ctx.Deadline()
return f.sent, f.err
}
func newAPI(token string, client api.ChatClient) *http.Server {
@@ -168,6 +193,10 @@ func TestNoPathIsExempt(t *testing.T) {
http.MethodPost, chatsPath, bearer,
http.StatusMethodNotAllowed, `{"error":"method not allowed"}` + "\n",
},
{
http.MethodPost, messagesPath, "",
http.StatusUnauthorized, unauthorized,
},
} {
rec := request(t, srv, tc.method, tc.path, tc.auth)
if rec.Code != tc.want || rec.Body.String() != tc.body {
@@ -242,7 +271,7 @@ func TestHeaders(t *testing.T) {
{&fakeClient{}, chatsPath, bearer},
{&fakeClient{}, chatsPath, ""},
{&fakeClient{}, "/nothing", bearer},
{&fakeClient{err: errChat}, chatsPath, bearer},
{&fakeClient{contactsErr: errChat}, chatsPath, bearer},
} {
rec := request(t, newAPI(credential, tc.client),
http.MethodGet, tc.path, tc.auth)