llm browser
This commit is contained in:
1 parent
afed023152
commit
a4dbb75195
39 files changed
+3709
-58
No files matched your search
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.k6n.net/mats/fullstack-template/internal/api"
|
||||
"git.k6n.net/mats/fullstack-template/internal/browse"
|
||||
"git.k6n.net/mats/fullstack-template/internal/items"
|
||||
)
|
||||
|
||||
@@ -293,3 +294,100 @@ func TestCORSPreflight(t *testing.T) {
|
||||
t.Errorf("Allow-Origin for unknown origin = %q, want none", got)
|
||||
}
|
||||
}
|
||||
|
||||
// fakeFetcher is the same in-memory browse.Fetcher as
|
||||
// internal/browse/service_test.go, duplicated on purpose: test fakes stay
|
||||
// package-local. A nil response simulates a connection failure.
|
||||
type fakeFetcher struct {
|
||||
pages map[string]*browse.Response
|
||||
}
|
||||
|
||||
func (f fakeFetcher) Fetch(_ context.Context, url string) (*browse.Response, error) {
|
||||
r, ok := f.pages[url]
|
||||
if !ok {
|
||||
return &browse.Response{Status: 404, FinalURL: url, ContentType: "text/plain", Body: []byte("not found")}, nil
|
||||
}
|
||||
if r == nil {
|
||||
return nil, &browse.FetchError{URL: url, Err: errors.New("dial tcp: connection refused")}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func newBrowseTestServer(t *testing.T) (http.Handler, fakeFetcher) {
|
||||
t.Helper()
|
||||
pages := map[string]*browse.Response{}
|
||||
add := func(url, ct, body string) {
|
||||
pages[url] = &browse.Response{Status: 200, FinalURL: url, ContentType: ct, Body: []byte(body)}
|
||||
}
|
||||
add("https://site.test/", "text/html; charset=utf-8",
|
||||
`<!doctype html><html><head><title>Site</title><meta name="description" content="d"></head>`+
|
||||
`<body><h1>Hello</h1><p><a href="/docs">docs</a></p></body></html>`)
|
||||
add("https://site.test/llms.txt", "text/markdown", "# Site\n\n> summary\n\n## Docs\n\n- [Docs](https://site.test/docs): the docs\n")
|
||||
add("https://site.test/robots.txt", "text/plain", "User-agent: *\nAllow: /\n")
|
||||
return api.New(api.Deps{
|
||||
Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
|
||||
Items: items.NewService(newFakeRepo()),
|
||||
Browse: browse.NewService(fakeFetcher{pages: pages}),
|
||||
DB: stubPinger{},
|
||||
AllowedOrigins: []string{"http://localhost:5173"},
|
||||
}), fakeFetcher{pages: pages}
|
||||
}
|
||||
|
||||
func TestBrowseEndpoint(t *testing.T) {
|
||||
h, _ := newBrowseTestServer(t)
|
||||
|
||||
rec := do(t, h, http.MethodGet, `/api/v1/browse?url=https%3A%2F%2Fsite.test%2F`, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("browse = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var snap browse.Snapshot
|
||||
decodeBody(t, rec, &snap)
|
||||
if snap.FinalURL != "https://site.test/" || snap.Status != 200 {
|
||||
t.Errorf("snapshot head = %d %s", snap.Status, snap.FinalURL)
|
||||
}
|
||||
if snap.Page == nil || snap.Page.Title != "Site" {
|
||||
t.Errorf("page = %+v", snap.Page)
|
||||
}
|
||||
if !strings.Contains(snap.Markdown, "# Hello") || !strings.Contains(snap.Markdown, "[docs](https://site.test/docs)") {
|
||||
t.Errorf("markdown = %q", snap.Markdown)
|
||||
}
|
||||
if snap.Files.LlmsTxt == nil || !strings.Contains(snap.Files.LlmsTxt.Content, "# Site") {
|
||||
t.Errorf("llms.txt = %+v", snap.Files.LlmsTxt)
|
||||
}
|
||||
if len(snap.Checks) == 0 {
|
||||
t.Error("checks empty")
|
||||
}
|
||||
// Bare-domain input normalizes to https on the wire.
|
||||
rec = do(t, h, http.MethodGet, `/api/v1/browse?url=site.test`, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("bare domain = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrowseEndpointErrors(t *testing.T) {
|
||||
h, fetcher := newBrowseTestServer(t)
|
||||
|
||||
// Missing and malformed input map to validation_failed.
|
||||
for _, target := range []string{"/api/v1/browse", "/api/v1/browse?url=", "/api/v1/browse?url=ftp://x.test"} {
|
||||
rec := do(t, h, http.MethodGet, target, "")
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("GET %s = %d, want 400: %s", target, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// A dead site maps to 502 unavailable.
|
||||
fetcher.pages["https://dead.test/"] = nil
|
||||
rec := do(t, h, http.MethodGet, `/api/v1/browse?url=https%3A%2F%2Fdead.test%2F`, "")
|
||||
if rec.Code != http.StatusBadGateway {
|
||||
t.Errorf("dead site = %d, want 502: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var errBody struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
decodeBody(t, rec, &errBody)
|
||||
if errBody.Error.Code != "unavailable" {
|
||||
t.Errorf("error code = %q, want unavailable", errBody.Error.Code)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user