Add content: guard header order and the unconfirmed pointer (GPT-6 Astra)
Astra's fourth pass: no objection to the structural approach on its own, and two build-valid edits that broke real behaviour in Chromium and passed make ci. Moving setRequestHeader above open() in refresh() throws InvalidStateError, and the footer never refreshes. Every setRequestHeader in drop.js is now required to sit between its own request's open() and send(). Cutting the pointer's condition to `batch.stored > 0` hid "View documents" from a batch whose only file was unconfirmed — the case that most needs the list. The condition must now name both cases, joined by ||. Mutations, each built and seen red: Astra's two (the true move, not a copy), a header set after send(), the condition joined by &&, and the twenty from before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+46
−0
internal/memoriatest/notice_test.go+46 −0
CI passed — run details
CI log
=== vet === go vet ./... === fmt === === test === go test ./... ? amadan.net/oficina/memoria/cmd/memoria [no test files] ok amadan.net/oficina/memoria/internal/memoria 3.391s ok amadan.net/oficina/memoria/internal/memoria/chunk (cached) ok amadan.net/oficina/memoria/internal/memoria/fact (cached) ? amadan.net/oficina/memoria/internal/memoria/icons [no test files] ok amadan.net/oficina/memoria/internal/memoria/index (cached) ok amadan.net/oficina/memoria/internal/memoria/notion (cached) ok amadan.net/oficina/memoria/internal/memoria/provider (cached) ok amadan.net/oficina/memoria/internal/memoria/query (cached) ok amadan.net/oficina/memoria/internal/memoria/seal (cached) ok amadan.net/oficina/memoria/internal/memoria/sibling (cached) ok amadan.net/oficina/memoria/internal/memoriatest 1.663s ? amadan.net/oficina/memoria/scripts/writeestilo [no test files] ? amadan.net/oficina/memoria/tools/bench [no test files] ? amadan.net/oficina/memoria/tools/drive [no test files] ? amadan.net/oficina/memoria/tools/seed [no test files] ? amadan.net/oficina/memoria/tools/vendorassets [no test files] === migration-check === go run github.com/carlosframework/rastrillo/cmd/rastrillo migration check rastrillo migration check: models and migrations agree
diff --git a/internal/memoriatest/notice_test.go b/internal/memoriatest/notice_test.go| index 10d73da..b8f0b09 100644 |
| --- a/internal/memoriatest/notice_test.go |
| +++ b/internal/memoriatest/notice_test.go |
| @@ -180,6 +180,9 @@ func assignments(src, name string) int { |
| // assigns refreshHeader 2 times" |
| // (d) the setRequestHeader line deleted from refresh() → "drop.js sends |
| // refreshHeader in 0 places, want 1" |
| +// (e) Astra's: that line moved above req.open in refresh(). Chromium |
| +// throws InvalidStateError and the footer is never refreshed → |
| +// "req.setRequestHeader comes before req.open" |
| func TestTheDropScriptSendsTheHeaderThePageNames(t *testing.T) { |
| h := newApp(t) |
| rec := get(t, h, "/static/drop.js") |
| @@ -206,6 +209,28 @@ func TestTheDropScriptSendsTheHeaderThePageNames(t *testing.T) { |
| if strings.Contains(strings.ToLower(js), "x-memoria") { |
| t.Error("drop.js spells a header name of its own; the page is where it reads one") |
| } |
| + |
| + // A header set before open() throws, and after send() is too late. |
| + // Every request in the file, not just this one: the upload's Accept |
| + // header is what gets it a judgement instead of a redirect. |
| + setters := regexp.MustCompile(`(\w+)\.setRequestHeader\(`).FindAllStringSubmatchIndex(js, -1) |
| + if len(setters) == 0 { |
| + t.Fatal("drop.js sets no request headers, so the order check below proves nothing") |
| + } |
| + for _, m := range setters { |
| + v, at := js[m[2]:m[3]], m[0] |
| + open, send := strings.Index(js, v+".open("), strings.Index(js, v+".send(") |
| + if open < 0 || send < 0 { |
| + t.Errorf("%s.setRequestHeader has no %s.open and %s.send around it", v, v, v) |
| + continue |
| + } |
| + if at < open { |
| + t.Errorf("%s.setRequestHeader comes before %s.open; browsers throw InvalidStateError", v, v) |
| + } |
| + if at > send { |
| + t.Errorf("%s.setRequestHeader comes after %s.send; the request has already gone", v, v) |
| + } |
| + } |
| } |
| // The tray points at the document list, which is not on /add, and keeps |
| @@ -225,6 +250,10 @@ func TestTheDropScriptSendsTheHeaderThePageNames(t *testing.T) { |
| // `listed` 5 times" |
| // (c) Astra's: that line MOVED from hideTray() into put() → "hideTray() |
| // does not hide the pointer" and "put() touches the pointer" |
| +// (d) Astra's: the condition cut to `if (batch.stored > 0)`, so a batch |
| +// with only an unconfirmed file hides the link to the one place its |
| +// fate can be checked → "the pointer is not shown for an unconfirmed |
| +// upload" |
| func TestTheTrayPointsAtTheDocumentList(t *testing.T) { |
| h := newApp(t) |
| doc := parsePage(t, get(t, h, "/add").Body.String()) |
| @@ -254,4 +283,21 @@ func TestTheTrayPointsAtTheDocumentList(t *testing.T) { |
| if strings.Count(js, "listed.hidden = false;") != 1 { |
| t.Error("drop.js does not show the pointer in exactly one place") |
| } |
| + // Shown when a file was stored OR its fate is unknown. An unconfirmed |
| + // file is the case that most needs the list: it is the only place to |
| + // find out whether the file landed. |
| + shown := regexp.MustCompile(`if \(([^)]*)\) \{\s*listed\.hidden = false;`).FindStringSubmatch(js) |
| + if shown == nil { |
| + t.Fatal("the pointer is not shown under an if, so its condition cannot be checked") |
| + } |
| + for _, want := range []string{"batch.stored > 0", "batch.unsure.length > 0"} { |
| + if !strings.Contains(shown[1], want) { |
| + t.Errorf("the pointer is not shown for %s: its condition is %q", |
| + map[string]string{"batch.stored > 0": "a stored upload", |
| + "batch.unsure.length > 0": "an unconfirmed upload"}[want], shown[1]) |
| + } |
| + } |
| + if !strings.Contains(shown[1], "||") || strings.Contains(shown[1], "&&") { |
| + t.Errorf("the pointer's condition is %q; either case alone must show it", shown[1]) |
| + } |
| } |