oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1package memoriatest
2
3import (
4 "net/http"
5 "regexp"
6 "strings"
7 "testing"
8)
9
10func TestStoreThenFind(t *testing.T) {
11 h := newApp(t)
12 if rec := store(t, h, "Q3 board pack", "## Revenue\n\nJune revenue was 1,350,000 across all regions.\n\n## Costs\n\nHosting fell after the migration."); rec.Code != http.StatusSeeOther {
13 t.Fatalf("store: status %d, want 303", rec.Code)
14 }
15 body := get(t, h, "/?q=june+revenue").Body.String()
16 if !strings.Contains(body, "Q3 board pack") {
17 t.Errorf("search does not show the document title:\n%s", body)
18 }
19 if !strings.Contains(body, "Revenue") {
20 t.Error("search result carries no heading trail")
21 }
22 if !strings.Contains(body, "<mark>") {
23 t.Error("search result carries no highlight")
24 }
25}
26
27// The list must distinguish "nothing matched" from "nothing indexed".
28// Conflating them is the plausible-empty-list failure.
29func TestNoMatchSaysTheCorpusIsNotEmpty(t *testing.T) {
30 h := newApp(t)
31 store(t, h, "Notes", strings.Repeat("hosting migration notes. ", 20))
32 body := get(t, h, "/?q=xylophone").Body.String()
33 // One quiet line (shared chrome §4), not "0 results" over a callout.
34 if !strings.Contains(body, "No matches for “xylophone”.") {
35 t.Errorf("no-match page does not say nothing matched:\n%s", body)
36 }
37 // The count is on the same page, in the footer, and it is not zero —
38 // so a reader can tell an absent answer from an empty index.
39 if !regexp.MustCompile(`[1-9][0-9]* passages? indexed`).MatchString(body) {
40 t.Error("no-match page does not say how much is indexed, so a reader " +
41 "cannot tell an absent answer from an empty index")
42 }
43}
44
45// Stored text is untrusted. A document containing markup must not
46// execute in a search result or on the document page.
47func TestStoredMarkupIsEscaped(t *testing.T) {
48 h := newApp(t)
49 const nasty = `<script>alert('xss')</script>`
50 store(t, h, "Payload "+nasty, "## Heading "+nasty+"\n\nA passage containing "+nasty+" inline and revenue too.")
51
52 for _, target := range []string{"/?q=revenue", "/documents/1"} {
53 body := get(t, h, target).Body.String()
54 if strings.Contains(body, "<script>") {
55 t.Errorf("%s renders an unescaped <script>:\n%s", target, body)
56 }
57 if !strings.Contains(body, "&lt;script&gt;") {
58 t.Errorf("%s does not show the escaped form, so the payload may have been dropped "+
59 "rather than escaped — check the snippet path", target)
60 }
61 }
62}
63
64// Re-storing under the same key is idempotent. The key is the filename
65// for an upload and the title for a paste, so pasting the same text twice
66// under the same title updates one document.
67func TestReStoringUnderTheSameKeyDoesNotDuplicate(t *testing.T) {
68 h := newApp(t)
69 const text = "## Revenue\n\nJune revenue was 1,350,000 across every region we serve."
70 store(t, h, "Board pack", text)
71 store(t, h, "Board pack", text)
72 if body := get(t, h, "/").Body.String(); !strings.Contains(body, "1 document") {
73 t.Errorf("re-storing under one key made more than one document:\n%s", body)
74 }
75}
76
77// Two different documents that happen to share text are two documents.
78// Under the old scheme — external_id as a hash of the body — they
79// collided into one, and the second silently overwrote the first's title.
80func TestSameTextUnderDifferentKeysIsTwoDocuments(t *testing.T) {
81 h := newApp(t)
82 const text = "## Revenue\n\nJune revenue was 1,350,000 across every region we serve."
83 store(t, h, "Board pack", text)
84 store(t, h, "Minutes", text)
85 body := get(t, h, "/").Body.String()
86 if !strings.Contains(body, "2 documents") {
87 t.Errorf("two documents sharing text collapsed into one:\n%s", body)
88 }
89 for _, want := range []string{"Board pack", "Minutes"} {
90 if !strings.Contains(body, want) {
91 t.Errorf("%q is missing from the list", want)
92 }
93 }
94}
95
96// A changed document must not leave its old passages searchable: a
97// citation to text that is no longer there is worse than a missing one.
98func TestRevisingRemovesTheOldPassages(t *testing.T) {
99 h := newApp(t)
100 store(t, h, "Doc", "## Revenue\n\nThe zebrafinch figure was 100.")
101 if !strings.Contains(get(t, h, "/?q=zebrafinch").Body.String(), "Doc") {
102 t.Fatal("the first version was not indexed")
103 }
104 // Same title, different body: a new document under this design,
105 // since the hash differs. Delete the first and confirm it is gone.
106 if rec := post(t, h, "/documents/1/delete", nil); rec.Code != http.StatusSeeOther {
107 t.Fatalf("delete: status %d, want 303", rec.Code)
108 }
109 body := get(t, h, "/?q=zebrafinch").Body.String()
110 if strings.Contains(body, "<mark>") {
111 t.Errorf("a deleted document is still searchable:\n%s", body)
112 }
113}
114
115func TestMissingDocument404s(t *testing.T) {
116 if rec := get(t, newApp(t), "/documents/999"); rec.Code != http.StatusNotFound {
117 t.Errorf("GET /documents/999: status %d, want 404", rec.Code)
118 }
119}
120
121func TestEmptySubmissionIsRefusedWithCopy(t *testing.T) {
122 h := newApp(t)
123 if rec := store(t, h, "", " "); rec.Code != http.StatusSeeOther {
124 t.Fatalf("empty store: status %d, want 303", rec.Code)
125 }
126 if !strings.Contains(get(t, h, "/add?err=empty").Body.String(), "nothing to store") {
127 t.Error("the empty-submission page does not say what went wrong")
128 }
129}
130
131// The footer states slice 0's honest claim on every screen.
132//
133// This test used to assert "nothing leaves this instance", which is how a
134// false claim stayed put: the copy was wrong and the test held it there.
135// Sign-in fetches https://<the address's domain>/.well-known/keymail
136// before anyone is admitted, so something does leave. What is true, and
137// what the product can support, is that no *document* does.
138func TestFooterStatesTheClaimItCanSupport(t *testing.T) {
139 body := get(t, newApp(t), "/").Body.String()
140 if !strings.Contains(body, "no document ever") {
141 t.Errorf("the page does not state the document-egress property:\n%s", body)
142 }
143 if strings.Contains(body, "nothing leaves this instance") {
144 t.Error("the page overclaims: sign-in dials the address's domain")
145 }
146}
147
148// The heading has its own indexed column, so it must not also be inside
149// the body column: it would count twice in BM25 and appear at the front
150// of every snippet. Caught by reading a rendered page, not by a test —
151// hence this one.
152func TestSnippetDoesNotRepeatTheHeading(t *testing.T) {
153 h := newApp(t)
154 store(t, h, "Board pack", "## Revenue\n\nJune revenue was 1,350,000 across all regions and every product line we sell today.")
155 body := get(t, h, "/?q=june+revenue").Body.String()
156
157 // Find the snippet paragraph and check it does not open with the
158 // heading text.
159 i := strings.Index(body, "rst-result-snippet")
160 if i < 0 {
161 t.Fatalf("no snippet rendered:\n%s", body)
162 }
163 snippet := body[i:min(i+300, len(body))]
164 // The heading is "Revenue"; the passage itself begins "June revenue".
165 if strings.Contains(snippet, "Revenue</mark> <mark>June") || strings.Contains(snippet, "Revenue June revenue") {
166 t.Errorf("the snippet repeats the heading before the body:\n%s", snippet)
167 }
168}
169
170// A search result links to "#p{n}" using the ordinal stored at ingest.
171// The document page used to re-run the chunker at view time, so any
172// change to chunking silently misaligned every citation on every existing
173// document — and this branch changed the chunker twice.
174func TestCitedAnchorExistsOnTheDocumentPage(t *testing.T) {
175 h := newApp(t)
176 store(t, h, "Board pack",
177 "## Revenue\nJune revenue was 1,350,000 across all regions.\n\n"+
178 "## Costs\nHosting fell after the migration and support was flat.\n\n"+
179 "## Risks\nOne customer is 22% of recurring revenue, which is the concentration risk.")
180
181 results := get(t, h, "/?q=concentration").Body.String()
182 m := regexp.MustCompile(`/documents/(\d+)#p(\d+)`).FindStringSubmatch(results)
183 if m == nil {
184 t.Fatalf("no citation anchor in the results:\n%s", results)
185 }
186 page := get(t, h, "/documents/"+m[1]).Body.String()
187 anchor := `id="p` + m[2] + `"`
188 if !strings.Contains(page, anchor) {
189 t.Errorf("the search cited %s but the document page has no %s — "+
190 "the citation points at nothing", m[0], anchor)
191 }
192}
193
194// A document edited and re-ingested under the same key must UPDATE, not
195// accumulate. external_id used to be a hash of the body, so every edit
196// produced a second document with the first still indexed under the same
197// title — and Revision, which the design leans on for staleness, could
198// only ever move for a byte-identical re-upload. Found by a code review.
199func TestEditingUnderTheSameKeyUpdatesOneDocument(t *testing.T) {
200 h := newApp(t)
201 const key = "board-pack.md"
202 upload(t, h, key, "## Revenue\nThe zebrafinch figure was 100 for the quarter just gone.")
203 upload(t, h, key, "## Revenue\nThe wildebeest figure was 200 for the quarter just gone.")
204
205 list := get(t, h, "/").Body.String()
206 if !strings.Contains(list, "1 document") {
207 t.Errorf("editing under the same key made more than one document:\n%s", list)
208 }
209 // The old passage must be gone, not merely outranked.
210 if got := get(t, h, "/?q=zebrafinch").Body.String(); strings.Contains(got, "<mark>") {
211 t.Error("the superseded passage is still searchable; a citation would point at text that is no longer there")
212 }
213 if got := get(t, h, "/?q=wildebeest").Body.String(); !strings.Contains(got, "<mark>") {
214 t.Error("the new passage was not indexed")
215 }
216}
217
218// A term that appears only in the document's title must find the
219// document. Reported from the live instance: a paste titled "Passport
220// numbers" whose body was numbers could not be found by "passport", and
221// the page said so confidently — "1 passage indexed, so this is a real
222// absence". It was not an absence. The index held two columns, heading
223// and body, and neither is the title: a chunk's heading trail comes from
224// Markdown headings *inside* the body, so a plain paste has none at all.
225// The title was sealed, shown on every result, and never indexed.
226func TestATermOnlyInTheTitleIsFound(t *testing.T) {
227 h := newApp(t)
228 store(t, h, "Passport numbers", "L898902C3\n\n7727833B1\n\n3041005D9")
229 body := get(t, h, "/?q=passport").Body.String()
230 // Count the result rows, not the title text. The same page lists
231 // every document below the results, so asserting on the title alone
232 // passes on a 0-result page — which is how the first draft of this
233 // test passed against the very bug it was written for.
234 if n := strings.Count(body, "rst-result-title"); n != 1 {
235 t.Errorf("a term that appears only in the title returns %d results, want 1:\n%s", n, body)
236 }
237}
238
239// The title must not drown the body. Indexing the title into every chunk
240// makes it a term in each one; this checks a body match still ranks and
241// that the title has not become the only thing that matches.
242func TestBodyStillMatchesWhenTitleIsIndexed(t *testing.T) {
243 h := newApp(t)
244 store(t, h, "Passport numbers", "The renewal fee rose in June.")
245 body := get(t, h, "/?q=renewal").Body.String()
246 if n := strings.Count(body, "rst-result-title"); n != 1 {
247 t.Errorf("body term returns %d results, want 1:\n%s", n, body)
248 }
249}
250