oficina / memoria Public

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>
Paul Campbell pushed by paul@keymail.dev f96bacb516eba806ce79d3b0acea8347fb1df0a7 parent 8d04049
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])
+ }
}