mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
c49a8ef806
* fix(history): split activity runs by source, stamp version source, stop dropping field changes (PLAN-2348 U1) Three server defects the History tab proposal measured (PLAN-2348 checkpoint 2), fixed without a schema change: - The activity debounce key gains source. One person editing in the web UI and then from the CLI merged into one row carrying the second door's source and time, and the value in between vanished. - The item update handler stamps VersionSource from the request when the body sent none. Every non-collab body edit reached the version row with no source, so UpdateItem's "web" default labelled an agent's API edit as a web edit. VersionSource, not Source, so items.source still does not move on an update. - The timeline stops dropping real field changes: an "updated" row that shared its second with a version (a status change sent with a body edit) and an "updated" row a comment links to (an update sent with a comment, whose change the comment card does not render, and which comments moving to Details hid from every view). "commented" rows stay excluded. The two old skips are replaced by one question: does the row record a change. Claude-Session: https://claude.ai/code/session_01WiJ1kQCbNUdFRyq2FEi4tL * docs(history): prose the source split and the updated-row exception falsified (PLAN-2348 U1) * fix(history): keep bulk and attribution-only update rows on their old rules; spare only linked updates that record a change (PLAN-2348 U1, codex r1) * fix(history): an update row saying nothing never renders, instead of hanging on a clock boundary (PLAN-2348 U1) * test(history): run the linked-rows query leg on Postgres too, where metadata is JSONB (PLAN-2348 U1)
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/PerpetualSoftware/pad/internal/models"
|
|
)
|
|
|
|
// PLAN-2348 checkpoint 2, defect 3. The update handler never gave the version
|
|
// row a source, so UpdateItem's "web" default labelled every body edit as a
|
|
// web edit: an agent's PATCH over a bearer token read "Web" in the Versions
|
|
// panel. Each leg writes a body through the real route and reads the version
|
|
// row that write produced, identified by the body it holds (a version row
|
|
// stores the body from BEFORE its edit).
|
|
func TestUpdateItem_VersionSourceIsTheRequestSource(t *testing.T) {
|
|
t.Parallel()
|
|
srv := testServer(t)
|
|
token, ws, slug := debounceFixture(t, srv)
|
|
path := "/api/v1/workspaces/" + ws + "/items/" + slug
|
|
|
|
// Bearer + X-Pad-Agent: the CLI/agent door.
|
|
var item models.Item
|
|
rr := authedAgentRequest(t, srv, token, "wren", "PATCH", path, map[string]any{"content": "agent body\n"})
|
|
decodeAttributionBody(t, rr, &item)
|
|
|
|
// Cookie session: the web door, on the same account.
|
|
data, _ := json.Marshal(map[string]any{"content": "web body\n"})
|
|
req := httptest.NewRequest("PATCH", path, bytes.NewReader(data))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.RemoteAddr = "192.0.2.1:1234"
|
|
req.AddCookie(&http.Cookie{Name: sessionCookieName(srv.secureCookies), Value: token})
|
|
const csrf = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
req.AddCookie(&http.Cookie{Name: "pad_csrf", Value: csrf})
|
|
req.Header.Set("X-CSRF-Token", csrf)
|
|
web := httptest.NewRecorder()
|
|
srv.ServeHTTP(web, req)
|
|
if web.Code != http.StatusOK {
|
|
t.Fatalf("cookie PATCH = %d: %s", web.Code, web.Body.String())
|
|
}
|
|
|
|
versions, err := srv.store.ListItemVersionsResolved(item.ID, "web body\n")
|
|
if err != nil {
|
|
t.Fatalf("list versions: %v", err)
|
|
}
|
|
// The body each write REPLACED names the version row that write made.
|
|
want := map[string]struct{ source, createdBy string }{
|
|
"": {"cli", "agent"}, // the agent's edit replaced the empty create body
|
|
"agent body\n": {"web", "user"},
|
|
}
|
|
seen := 0
|
|
for _, v := range versions {
|
|
w, ok := want[v.Content]
|
|
if !ok {
|
|
continue
|
|
}
|
|
seen++
|
|
if v.Source != w.source {
|
|
t.Errorf("version holding %q: source = %q, want %q", v.Content, v.Source, w.source)
|
|
}
|
|
if v.CreatedBy != w.createdBy {
|
|
t.Errorf("version holding %q: created_by = %q, want %q", v.Content, v.CreatedBy, w.createdBy)
|
|
}
|
|
}
|
|
if seen != len(want) {
|
|
t.Fatalf("found %d of %d expected version rows; versions: %+v", seen, len(want), versions)
|
|
}
|
|
}
|