feat: add title and description fields to capture grammar
Implement | prefix for titles and // separator for descriptions across the full stack: parser, schema, API, CLI, and web frontend. - Parser: line-aware extraction for |title, |title // desc, // leading desc, body // inline desc. URL-safe (skips :// lines). Modifiers (#tag, @time, ^card) extracted from all segments. - Schema: ALTER TABLE migration adds title, description columns - DB: Entity/EntityUpdate structs, all CRUD queries updated - API: title/description on create/update/response, body validation relaxed (title-only entries valid) - CLI: shows title as scan label when present - Web: parseInput mirrors Go parser, list shows title, detail pane renders title + description with double-click inline editing - Tests: 10 new cases (grammar, entity, API) — 71 total, all pass
This commit is contained in:
@@ -578,6 +578,115 @@ func TestAbsorbEntity_MissingSourceID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEntity_WithTitle(t *testing.T) {
|
||||
srv, _ := testServer(t)
|
||||
|
||||
resp := postJSON(t, srv, "/api/entities", map[string]any{
|
||||
"body": "body text",
|
||||
"title": "nginx trick",
|
||||
"description": "always forget this",
|
||||
"tags": []string{"ops"},
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var e EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&e)
|
||||
if e.Title == nil || *e.Title != "nginx trick" {
|
||||
t.Errorf("title: %v", e.Title)
|
||||
}
|
||||
if e.Description == nil || *e.Description != "always forget this" {
|
||||
t.Errorf("description: %v", e.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEntity_TitleOnly(t *testing.T) {
|
||||
srv, _ := testServer(t)
|
||||
|
||||
title := "title only"
|
||||
resp := postJSON(t, srv, "/api/entities", map[string]any{
|
||||
"title": title,
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var e EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&e)
|
||||
if e.Title == nil || *e.Title != "title only" {
|
||||
t.Errorf("title: %v", e.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateEntity_Title(t *testing.T) {
|
||||
srv, _ := testServer(t)
|
||||
created := createTestEntity(t, srv, "body", nil)
|
||||
|
||||
req, _ := http.NewRequest("PUT", srv.URL+"/api/entities/"+created.ID, bytes.NewReader(
|
||||
mustJSON(map[string]any{"title": "new title"})))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, _ := http.DefaultClient.Do(req)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var e EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&e)
|
||||
if e.Title == nil || *e.Title != "new title" {
|
||||
t.Errorf("title: %v", e.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateEntity_Description(t *testing.T) {
|
||||
srv, _ := testServer(t)
|
||||
created := createTestEntity(t, srv, "body", nil)
|
||||
|
||||
req, _ := http.NewRequest("PUT", srv.URL+"/api/entities/"+created.ID, bytes.NewReader(
|
||||
mustJSON(map[string]any{"description": "new desc"})))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, _ := http.DefaultClient.Do(req)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var e EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&e)
|
||||
if e.Description == nil || *e.Description != "new desc" {
|
||||
t.Errorf("description: %v", e.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEntities_TitleInResponse(t *testing.T) {
|
||||
srv, _ := testServer(t)
|
||||
|
||||
title := "list title"
|
||||
postJSON(t, srv, "/api/entities", map[string]any{
|
||||
"body": "body",
|
||||
"title": title,
|
||||
}).Body.Close()
|
||||
|
||||
resp, _ := http.Get(srv.URL + "/api/entities")
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&entities)
|
||||
if len(entities) != 1 {
|
||||
t.Fatalf("expected 1, got %d", len(entities))
|
||||
}
|
||||
if entities[0].Title == nil || *entities[0].Title != "list title" {
|
||||
t.Errorf("title: %v", entities[0].Title)
|
||||
}
|
||||
}
|
||||
|
||||
func mustJSON(v any) []byte {
|
||||
b, _ := json.Marshal(v)
|
||||
return b
|
||||
|
||||
Reference in New Issue
Block a user