mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-15 04:30:55 +08:00
New restores every unfinished task in the shared database and runs its planner, worker and deadline loops on the server context. Tests that passed context.Background() never stopped or joined them, so they kept writing under the manager TempDir after the test returned and the TempDir cleanup failed with directory not empty. Add stopTestServer, which cancels the context, waits for each task to go quiescent, joins its loops, the archive worker and the side-question writer, and defer it right after New in every test that started a live server.
168 lines
6.1 KiB
Go
168 lines
6.1 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
func TestTaskTemplateHTTPCRUD(t *testing.T) {
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
defer m.Close()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
s := New(ctx, m, t.TempDir(), t.TempDir(), t.TempDir())
|
|
defer stopTestServer(t, s, cancel)
|
|
h := s.Handler()
|
|
token, err := signJWT(s.jwtKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
do := func(method, path string, body any) (int, map[string]any) {
|
|
var data []byte
|
|
if body != nil {
|
|
data, _ = json.Marshal(body)
|
|
}
|
|
req := httptest.NewRequest(method, path, bytes.NewReader(data))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, req)
|
|
var out map[string]any
|
|
_ = json.Unmarshal(rec.Body.Bytes(), &out)
|
|
return rec.Code, out
|
|
}
|
|
|
|
name := fmt.Sprintf("http-template-%d", time.Now().UnixNano())
|
|
code, created := do(http.MethodPost, "/api/task-templates", map[string]any{
|
|
"name": name, "description": "description", "goal": "goal",
|
|
})
|
|
if code != http.StatusCreated {
|
|
t.Fatalf("create status=%d body=%v", code, created)
|
|
}
|
|
id := int64(created["id"].(float64))
|
|
t.Cleanup(func() { _, _ = m.pg.DeleteTaskTemplate(id) })
|
|
|
|
code, duplicate := do(http.MethodPost, "/api/task-templates", map[string]any{
|
|
"name": " " + name + " ", "description": "other", "goal": "other",
|
|
})
|
|
if code != http.StatusConflict {
|
|
t.Fatalf("duplicate status=%d body=%v", code, duplicate)
|
|
}
|
|
|
|
code, updated := do(http.MethodPatch, fmt.Sprintf("/api/task-templates/%d", id), map[string]any{"goal": "patched goal"})
|
|
if code != http.StatusOK || updated["goal"] != "patched goal" || updated["description"] != "description" {
|
|
t.Fatalf("patch status=%d body=%v", code, updated)
|
|
}
|
|
code, _ = do(http.MethodPatch, fmt.Sprintf("/api/task-templates/%d", id), map[string]any{
|
|
"goal": strings.Repeat("x", db.MaxTaskTemplateTextRunes+1),
|
|
})
|
|
if code != http.StatusBadRequest {
|
|
t.Fatalf("overlong patch status=%d, want %d", code, http.StatusBadRequest)
|
|
}
|
|
code, _ = do(http.MethodPost, "/api/task-templates", map[string]any{
|
|
"name": "oversized", "description": strings.Repeat("x", maxTaskTemplateRequestBytes+1), "goal": "goal",
|
|
})
|
|
if code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("oversized create status=%d, want %d", code, http.StatusRequestEntityTooLarge)
|
|
}
|
|
code, listed := do(http.MethodGet, "/api/task-templates", nil)
|
|
if code != http.StatusOK || listed["templates"] == nil {
|
|
t.Fatalf("list status=%d body=%v", code, listed)
|
|
}
|
|
code, deleted := do(http.MethodDelete, fmt.Sprintf("/api/task-templates/%d", id), nil)
|
|
if code != http.StatusOK || int64(deleted["deleted"].(float64)) != id {
|
|
t.Fatalf("delete status=%d body=%v", code, deleted)
|
|
}
|
|
}
|
|
|
|
func TestConversationPatchReturnsPinState(t *testing.T) {
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
defer m.Close()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
s := New(ctx, m, t.TempDir(), t.TempDir(), t.TempDir())
|
|
defer stopTestServer(t, s, cancel)
|
|
conversation, err := m.pg.CreateConversation("mainagent", "pin through http", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = m.pg.DeleteConversation(conversation.ID) })
|
|
token, err := signJWT(s.jwtKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]any{"title": " pinned title ", "pinned": true})
|
|
req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/api/conversations/%d", conversation.ID), bytes.NewReader(body))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("patch status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var updated struct {
|
|
Title string `json:"title"`
|
|
Pinned bool `json:"pinned"`
|
|
PinnedAt *string `json:"pinned_at"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &updated); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if updated.Title != "pinned title" || !updated.Pinned || updated.PinnedAt == nil {
|
|
t.Fatalf("unexpected conversation patch response: %+v", updated)
|
|
}
|
|
|
|
body, _ = json.Marshal(map[string]any{"title": strings.Repeat("x", maxConversationTitleRunes+1)})
|
|
req = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/api/conversations/%d", conversation.ID), bytes.NewReader(body))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("overlong conversation title status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
body, _ = json.Marshal(map[string]any{"title": strings.Repeat("x", maxConversationRequestBytes+1)})
|
|
req = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/api/conversations/%d", conversation.ID), bytes.NewReader(body))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("oversized conversation patch status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
body, _ = json.Marshal(map[string]any{
|
|
"agent_key": "mainagent", "title": strings.Repeat("x", maxConversationTitleRunes+1),
|
|
})
|
|
req = httptest.NewRequest(http.MethodPost, "/api/conversations", bytes.NewReader(body))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("overlong conversation create status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
body, _ = json.Marshal(map[string]any{
|
|
"agent_key": "mainagent", "title": strings.Repeat("x", maxConversationRequestBytes+1),
|
|
})
|
|
req = httptest.NewRequest(http.MethodPost, "/api/conversations", bytes.NewReader(body))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("oversized conversation create status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|