/events and /morning read the clock on his wall (V-612)

Three defects on the server-rendered pages.

/events printed both timestamps in whatever zone the value arrived in.
NoticedAt is the bus's local instant; OccurredAt is the store's UTC, or a
pubDate internal/rss parsed to UTC. So one row carried two zones and a feed
item read hours older than it was, on a page whose hint tells him that column
gap is real.

/morning printed a plan item's At raw. It is a calendar fact's Ts or a
reminder's FireTs, both UTC out of the store, so the same reminder named a
different hour here than on /reminders — which does call Local, since V-469.

promoteCandidate read the importance select inside `if due != nil`.
Confirming a candidate as "срочно" with no deadline threw the word away and
the row came back normal with nothing saying why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 04:23:46 +04:00
parent 0b1efe4911
commit 07f7550931
6 changed files with 171 additions and 9 deletions
+68
View File
@@ -32,6 +32,31 @@ type fakeTaskCore struct {
statusErr error
promoted bool
// The promote path's two extra writes.
fields []any
edits []editCall
editErr error
fieldErr error
}
// editCall records one EditTask, so a test can say what the form actually sent
// down rather than only that the promotion succeeded.
type editCall struct {
ID int64
Text string
Due *time.Time
Weight int
}
func (f *fakeTaskCore) EditTask(_ context.Context, id int64, text string, due *time.Time, weight int) error {
f.edits = append(f.edits, editCall{id, text, due, weight})
return f.editErr
}
func (f *fakeTaskCore) SetTaskFields(_ context.Context, id int64, doneWhen, blockedOn string) error {
f.fields = append(f.fields, []any{id, doneWhen, blockedOn})
return f.fieldErr
}
func (f *fakeTaskCore) ListTasks(_ context.Context, status string) ([]ipc.Task, error) {
@@ -220,6 +245,49 @@ func TestApplyTaskPostCarriesWeight(t *testing.T) {
}
}
// Confirming a candidate posts the importance select whether or not a date is
// set. The weight write hung off `if due != nil`, so "срочно" with no deadline
// was read off the form and thrown away, and the row came back normal.
func TestPromoteCandidateCarriesWeightWithoutADueDate(t *testing.T) {
core := &fakeTaskCore{}
form := url.Values{
"action": {"promote"}, "id": {"4"}, "text": {"продлить страховку"},
"done_when": {"полис на руках"}, "weight": {"3"},
}
req := httptest.NewRequest(http.MethodPost, "/tasks", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
handleTasks(httptest.NewRecorder(), req, core)
if len(core.edits) != 1 {
t.Fatalf("edits = %+v, want the weight written once", core.edits)
}
if core.edits[0].Weight != 3 || core.edits[0].ID != 4 {
t.Errorf("edit = %+v, want id 4 at weight 3", core.edits[0])
}
if core.edits[0].Due != nil {
t.Errorf("edit invented a due date: %v", core.edits[0].Due)
}
if core.statusVal != "open" {
t.Errorf("status = %q, want the candidate promoted", core.statusVal)
}
}
// A promote with neither field set still writes nothing: the row is unchanged
// apart from its status, and an EditTask here would be a no-op that can fail.
func TestPromoteCandidateWithNoDateAndNoWeightDoesNotEdit(t *testing.T) {
core := &fakeTaskCore{}
form := url.Values{
"action": {"promote"}, "id": {"4"}, "text": {"продлить страховку"},
"done_when": {"полис на руках"}, "weight": {"0"},
}
req := httptest.NewRequest(http.MethodPost, "/tasks", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
handleTasks(httptest.NewRecorder(), req, core)
if len(core.edits) != 0 {
t.Errorf("edits = %+v, want none", core.edits)
}
}
// Out of range clamps rather than 400s; a non-number is a real client error.
func TestApplyTaskPostClampsWeight(t *testing.T) {
core := &fakeTaskCore{created: true}