fix: harden API, DB schema, and CLI safety
- Add 'reminder' to glyph CHECK constraint (was accepted by parser but
rejected by DB)
- Default serve bind to 127.0.0.1, add --host flag for LAN access
- Validate card_data as JSON in Store.Create/Update/Promote
- Return pagination envelope {data,total,limit,offset} from list endpoint
- Append absorb breadcrumb to source entity before soft-delete
- Add Levenshtein fuzzy match to catch command typos before routing to add
- Replace DDL string-matching migrations with versioned schema_version table
- Update web UI and API tests for envelope response format
This commit is contained in:
+21
-14
@@ -25,6 +25,20 @@ func testServer(t *testing.T) (*httptest.Server, *db.Store) {
|
||||
return srv, store
|
||||
}
|
||||
|
||||
type listEnvelope struct {
|
||||
Data []EntityResponse `json:"data"`
|
||||
Total int `json:"total"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
|
||||
func decodeList(t *testing.T, resp *http.Response) []EntityResponse {
|
||||
t.Helper()
|
||||
var env listEnvelope
|
||||
json.NewDecoder(resp.Body).Decode(&env)
|
||||
return env.Data
|
||||
}
|
||||
|
||||
func postJSON(t *testing.T, srv *httptest.Server, path string, body any) *http.Response {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(body)
|
||||
@@ -157,8 +171,7 @@ func TestListEntities_Default(t *testing.T) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&entities)
|
||||
entities := decodeList(t, resp)
|
||||
if len(entities) != 2 {
|
||||
t.Fatalf("expected 2, got %d", len(entities))
|
||||
}
|
||||
@@ -175,8 +188,7 @@ func TestListEntities_FilterTag(t *testing.T) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&entities)
|
||||
entities := decodeList(t, resp)
|
||||
if len(entities) != 1 {
|
||||
t.Fatalf("expected 1, got %d", len(entities))
|
||||
}
|
||||
@@ -198,8 +210,7 @@ func TestListEntities_CardsOnly(t *testing.T) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&entities)
|
||||
entities := decodeList(t, resp)
|
||||
if len(entities) != 1 {
|
||||
t.Fatalf("expected 1 card, got %d", len(entities))
|
||||
}
|
||||
@@ -215,16 +226,14 @@ func TestListEntities_Pagination(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var page1 []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&page1)
|
||||
page1 := decodeList(t, resp)
|
||||
resp.Body.Close()
|
||||
|
||||
resp, err = http.Get(srv.URL + "/api/entities?limit=2&offset=2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var page2 []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&page2)
|
||||
page2 := decodeList(t, resp)
|
||||
resp.Body.Close()
|
||||
|
||||
if len(page1) != 2 || len(page2) != 2 {
|
||||
@@ -517,8 +526,7 @@ func TestAbsorbEntity_Success(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(listResp.Body).Decode(&entities)
|
||||
entities := decodeList(t, listResp)
|
||||
listResp.Body.Close()
|
||||
for _, ent := range entities {
|
||||
if ent.ID == source.ID {
|
||||
@@ -686,8 +694,7 @@ func TestListEntities_TitleInResponse(t *testing.T) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entities []EntityResponse
|
||||
json.NewDecoder(resp.Body).Decode(&entities)
|
||||
entities := decodeList(t, resp)
|
||||
if len(entities) != 1 {
|
||||
t.Fatalf("expected 1, got %d", len(entities))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user