repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

8.1 KB · 214 lines · Go Raw History
  1package main
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"chat.bythewood.me/tools"
  8)
  9
 10func testSources() []Source {
 11	return collectSources([]tools.Result{
 12		{Name: "web_search", Content: map[string]any{"results": []tools.SearchHit{
 13			{Title: "Portsmouth arrival", URL: "https://www.bbc.co.uk/news/articles/x", Snippet: "A boat carrying 140 people landed at Portsmouth"},
 14			{Title: "Crossings fall", URL: "https://example.org/stats?utm_source=ddg", Snippet: "crossings fell 43% over the year"},
 15		}}},
 16		{Name: "web_fetch", Content: map[string]any{
 17			"url":  "https://www.bbc.co.uk/news/articles/x#top",
 18			"text": "Border Force intercepted the dinghy off Portsmouth on Saturday and brought 140 people ashore. Hampshire Police opened an investigation into assaults on officers.",
 19		}},
 20		{Name: "weather", Content: map[string]any{"temp": 61}},
 21		{Name: "web_fetch", Err: "404", Content: map[string]any{"url": "https://example.com/gone"}},
 22	})
 23}
 24
 25func TestCollectSources(t *testing.T) {
 26	srcs := testSources()
 27	if len(srcs) != 2 {
 28		t.Fatalf("want 2 sources, got %d: %+v", len(srcs), srcs)
 29	}
 30	// The fetched page is first because it is what the model actually read,
 31	// and the fragment on it does not make it a second source.
 32	if srcs[0].N != 1 || srcs[0].Site != "bbc.co.uk" {
 33		t.Errorf("the fetched page should be [1], got %+v", srcs[0])
 34	}
 35	if !strings.Contains(srcs[0].Text, "Hampshire") {
 36		t.Error("the fetched page kept the snippet instead of the page text")
 37	}
 38	if srcs[0].Title != "Portsmouth arrival" {
 39		t.Errorf("the search hit's title was lost: %q", srcs[0].Title)
 40	}
 41	// A tracking parameter is not part of the address.
 42	if strings.Contains(srcs[1].URL, "utm_source") {
 43		t.Errorf("utm parameter survived: %s", srcs[1].URL)
 44	}
 45	// A tool with no page behind it and a failed fetch are not sources.
 46	for _, s := range srcs {
 47		if strings.Contains(s.URL, "gone") {
 48			t.Error("a failed fetch was offered as a source")
 49		}
 50	}
 51}
 52
 53func TestAttachKeepsAndRepairs(t *testing.T) {
 54	srcs := testSources()
 55
 56	// A number the model wrote that names a real source is kept, and one that
 57	// names nothing is dropped rather than shown as text.
 58	got := attach("Border Force brought 140 people ashore at Portsmouth [1]. The rest is guesswork [9].", srcs)
 59	if !strings.Contains(got, "Portsmouth.[1]") {
 60		t.Errorf("the model's own citation was not kept: %q", got)
 61	}
 62	if strings.Contains(got, "[9]") {
 63		t.Errorf("a citation with no source behind it survived: %q", got)
 64	}
 65
 66	// A sentence carrying a source's own figures and names gets one even
 67	// though the model wrote none.
 68	got = attach("Hampshire Police opened an investigation into assaults on officers.", srcs)
 69	if !strings.Contains(got, "[1]") {
 70		t.Errorf("a sentence lifted from the page was left uncited: %q", got)
 71	}
 72
 73	// A sentence about nothing in particular gets nothing, since a pill on an
 74	// unsupported line reads as a check that passed.
 75	got = attach("That is worth thinking about before you decide anything.", srcs)
 76	if strings.Contains(got, "[") {
 77		t.Errorf("an unsupported sentence was given a source: %q", got)
 78	}
 79}
 80
 81func TestAttachLeavesCodeAlone(t *testing.T) {
 82	srcs := testSources()
 83	src := "Read it like this:\n\n```go\nfmt.Println(rows[1])\n```\n\nThen use `cols[1]` after."
 84	got := attach(src, srcs)
 85	if !strings.Contains(got, "rows[1])") {
 86		t.Errorf("an index inside a fence was rewritten: %q", got)
 87	}
 88	if !strings.Contains(got, "`cols[1]`") {
 89		t.Errorf("an index inside a code span was rewritten: %q", got)
 90	}
 91}
 92
 93func TestAttachMovesTheMarkerToTheEnd(t *testing.T) {
 94	srcs := testSources()
 95	// The model puts it after the full stop about as often as before it, and a
 96	// pill has to land in the same place either way.
 97	for _, in := range []string{
 98		"Border Force brought 140 people ashore at Portsmouth. [1] It was Saturday.",
 99		"Border Force brought 140 people ashore at Portsmouth [1]. It was Saturday.",
100	} {
101		got := attach(in, srcs)
102		if !strings.Contains(got, "Portsmouth.[1]") {
103			t.Errorf("marker not normalised for %q, got %q", in, got)
104		}
105		if strings.Count(got, "[1]") != 1 {
106			t.Errorf("the marker was duplicated: %q", got)
107		}
108	}
109}
110
111func TestDropSourceList(t *testing.T) {
112	// What the model wrote under an answer before it had numbers, and every
113	// line of it was dead text rather than a link.
114	answer := "The crossings are down 43% on last year.\n\nSo the framing does not hold.\n\nbbc.co.uk/news/articles/crl60z17lyko\nindependent.co.uk/news/uk/home-news/portsmouth-b3045873.html"
115	got := dropSourceList(answer)
116	if strings.Contains(got, "bbc.co.uk") || strings.Contains(got, "independent") {
117		t.Errorf("the address dump survived: %q", got)
118	}
119	if !strings.Contains(got, "the framing does not hold") {
120		t.Errorf("the answer above it was eaten: %q", got)
121	}
122
123	// The heading form, and the same thing written on one line.
124	if got := dropSourceList("It is down.\n\nSources: https://a.org/x and https://b.org/y"); strings.Contains(got, "a.org") {
125		t.Errorf("a Sources: line survived: %q", got)
126	}
127
128	// A bulleted list of links in the middle of an answer is the answer, and
129	// only a Sources heading turns one at the end into a dump.
130	links := "Three places sell it:\n\n- example.org/parts/a\n- example.org/parts/b"
131	if got := dropSourceList(links); got != links {
132		t.Errorf("a list the reader asked for was dropped: %q", got)
133	}
134	withHeading := links + "\n\nSources\n\n- example.org/parts/a"
135	if got := dropSourceList(withHeading); !strings.HasSuffix(got, "parts/b") {
136		t.Errorf("a headed dump was not trimmed back to the answer: %q", got)
137	}
138
139	// An answer ending on a real sentence is untouched, including one that
140	// mentions a site by name.
141	for _, keep := range []string{
142		"It is down 43% and nobody expected that.",
143		"Check the Home Office quarterly table, which is the number that settles it.",
144	} {
145		if got := dropSourceList(keep); got != keep {
146			t.Errorf("prose was trimmed: %q became %q", keep, got)
147		}
148	}
149}
150
151func TestLinkBareAddresses(t *testing.T) {
152	got := linkBareAddresses("It is on bbc.co.uk/news/articles/x now.")
153	if !strings.Contains(got, "https://bbc.co.uk/news/articles/x") {
154		t.Errorf("a schemeless address was left dead: %q", got)
155	}
156	// Already a link, code, and a version number are all left alone.
157	for _, keep := range []string{
158		"See https://bbc.co.uk/news/articles/x for it.",
159		"Run `go/bin/thing` first.",
160		"The ratio is 1.2/3 either way.",
161	} {
162		if got := linkBareAddresses(keep); got != keep {
163			t.Errorf("%q was rewritten to %q", keep, got)
164		}
165	}
166}
167
168func TestLinkCitations(t *testing.T) {
169	srcs := testSources()
170	got := linkCitations("<p>Ashore at Portsmouth[1].</p>", srcs)
171	if !strings.Contains(got, `href="https://www.bbc.co.uk/news/articles/x"`) {
172		t.Errorf("the pill did not link to the source: %q", got)
173	}
174	// A number with no source behind it stays as text rather than becoming a
175	// link to nothing.
176	if got := linkCitations("<p>Something[7].</p>", srcs); strings.Contains(got, "<a") {
177		t.Errorf("an unknown number was linked: %q", got)
178	}
179	// Code is left alone, since an index is not a citation.
180	if got := linkCitations("<pre><code>rows[1]</code></pre>", srcs); strings.Contains(got, "<a") {
181		t.Errorf("an index inside code was linked: %q", got)
182	}
183	// A digit in an attribute is not a citation either.
184	if got := linkCitations(`<p data-x="[1]">hi</p>`, srcs); strings.Contains(got, "<a") {
185		t.Errorf("an attribute was rewritten: %q", got)
186	}
187}
188
189func TestCited(t *testing.T) {
190	srcs := testSources()
191	out := cited("One thing[2]. Another thing.", srcs)
192	if len(out) != 1 || out[0].N != 2 {
193		t.Errorf("want only source 2, got %+v", out)
194	}
195	if len(cited("Nothing cited here.", srcs)) != 0 {
196		t.Error("an answer citing nothing listed sources anyway")
197	}
198}
199
200func TestSentences(t *testing.T) {
201	got := sentences("It landed at 4am. Police opened a case. Nobody was charged.")
202	if len(got) != 3 {
203		t.Fatalf("want 3 sentences, got %d: %q", len(got), got)
204	}
205	// A decimal and an abbreviation are not sentence ends, and a code span
206	// carrying a full stop is stepped over.
207	if got := sentences("The figure is 43.5% of the total."); len(got) != 1 {
208		t.Errorf("a decimal split a sentence: %q", got)
209	}
210	if got := sentences("Call `fmt.Println` for it."); len(got) != 1 {
211		t.Errorf("a code span split a sentence: %q", got)
212	}
213}