check / check (push) Successful in 1m13s
The bot now serves an HTTP API on `PORT` (default 8080) beside the chat client, whose WebSocket stays on 127.0.0.1 inside the container. Every request needs `Authorization: Bearer` with the credential from the file named by `API_TOKEN_FILE`, compared in constant time; with no credential configured every request is refused, `OPTIONS *` included. `GET /api/v1/chats` lists the bot's chats. Responses carry the security headers from the repository policies; bodies, requests and the server are time- and size-bounded. The chat client stops only after the API has finished its requests. Disclosures: `contact_deleted` is an extra field; 404 and 405 answer in JSON; requests net/http cannot parse are refused by net/http without the security headers; three gosec findings are suppressed as false positives. Model: opus-5-5
65 lines
2.1 KiB
Go
65 lines
2.1 KiB
Go
package api_test
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/simplexcalc/internal/simplex"
|
|
)
|
|
|
|
// TestChats: the chats are the bot's contacts, ordered by id, deleted
|
|
// ones marked, and the chat client is asked for the bot's user with a
|
|
// deadline.
|
|
func TestChats(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeClient{contacts: []simplex.Contact{
|
|
{ContactID: 3, Profile: simplex.Profile{DisplayName: "bob"}, Status: "deleted"},
|
|
{ContactID: 2, Profile: simplex.Profile{DisplayName: "alice"}, Status: "active"},
|
|
}}
|
|
|
|
rec := request(t, newAPI(credential, client), http.MethodGet, chatsPath, bearer)
|
|
|
|
want := `{"chats":[{"id":2,"display_name":"alice","contact_deleted":false},` +
|
|
`{"id":3,"display_name":"bob","contact_deleted":true}]}` + "\n"
|
|
if rec.Code != http.StatusOK || rec.Body.String() != want {
|
|
t.Errorf("response = %d %q, want 200 %q", rec.Code, rec.Body.String(), want)
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); got != "application/json" {
|
|
t.Errorf("Content-Type = %q, want application/json", got)
|
|
}
|
|
|
|
if client.userID != 1 || !client.hadDeadline {
|
|
t.Errorf("the chat client was asked for user %d, deadline %v; "+
|
|
"want user 1 with a deadline", client.userID, client.hadDeadline)
|
|
}
|
|
}
|
|
|
|
// TestNoChats: no contacts is an empty list, not null.
|
|
func TestNoChats(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rec := request(t, newAPI(credential, &fakeClient{}),
|
|
http.MethodGet, chatsPath, bearer)
|
|
|
|
want := `{"chats":[]}` + "\n"
|
|
if rec.Code != http.StatusOK || rec.Body.String() != want {
|
|
t.Errorf("response = %d %q, want 200 %q", rec.Code, rec.Body.String(), want)
|
|
}
|
|
}
|
|
|
|
// TestChatsFailure: when the chat client fails, the response says so
|
|
// in a chosen sentence, never in the error's own text.
|
|
func TestChatsFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rec := request(t, newAPI(credential, &fakeClient{err: errChat}),
|
|
http.MethodGet, chatsPath, bearer)
|
|
|
|
want := `{"error":"the chats could not be read"}` + "\n"
|
|
if rec.Code != http.StatusInternalServerError || rec.Body.String() != want {
|
|
t.Errorf("response = %d %q, want 500 %q", rec.Code, rec.Body.String(), want)
|
|
}
|
|
}
|