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

GET /api/v1/chats/{id}/messages returns the messages among a chat's
last count items (default 20, at most 100), oldest first. POST sends a
text, waits for the chat client's answer and returns 201 with the
message as sent. A chat the bot does not have is 404, a contact who
deleted the chat is 409, a text too long for one message is 413, and
any other failure is 500 with a chosen sentence.

The chat client spells out a message's formatting in its answer, up to
26 times the text's length, so 100 items in one answer can pass the
16 MiB read limit and end the connection. Items are read five at a
time.

Model: opus-5-5
This commit is contained in:
2026-09-29 03:48:45 +00:00
parent 8f0906d677
commit 2ad0b68e35
10 changed files with 1018 additions and 28 deletions
+30 -3
View File
@@ -22,19 +22,24 @@ 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 err, and remembers what it was asked.
type fakeClient struct {
contacts []simplex.Contact
items []simplex.ChatItem
sent simplex.ChatItem
err error
userID int64
hadDeadline bool
userID, contactID int64
count int
text string
hadDeadline bool
}
func (f *fakeClient) Contacts(
@@ -46,6 +51,24 @@ func (f *fakeClient) Contacts(
return f.contacts, f.err
}
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 {
return api.New(api.Params{
Log: slog.New(slog.DiscardHandler),
@@ -168,6 +191,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 {