fix
This commit is contained in:
+80
-11
@@ -2,26 +2,46 @@ package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"wh/api"
|
||||
"wh/config"
|
||||
"wh/store"
|
||||
"wh/web"
|
||||
"halcyon/api"
|
||||
"halcyon/config"
|
||||
"halcyon/store"
|
||||
"halcyon/web"
|
||||
)
|
||||
|
||||
// newSite starts the API with the web front end mounted, as cmd/server does.
|
||||
func newSite(t *testing.T) (*httptest.Server, config.Config) {
|
||||
func newSite(t *testing.T, manualPDF string) (*httptest.Server, config.Config) {
|
||||
srv, cfg, _ := newSiteStore(t, manualPDF)
|
||||
return srv, cfg
|
||||
}
|
||||
|
||||
// newSiteStore is newSite, also returning the database so tests can run a simulated day.
|
||||
func newSiteStore(t *testing.T, manualPDF string) (*httptest.Server, config.Config, *store.Store) {
|
||||
t.Helper()
|
||||
cfg := config.Default()
|
||||
st, err := store.Open("sqlite:" + filepath.Join(t.TempDir(), "t.db"))
|
||||
dsn := "sqlite:" + filepath.Join(t.TempDir(), "t.db")
|
||||
if pg := os.Getenv("HALCYON_TEST_PG"); pg != "" { // run against PostgreSQL, wiping its public schema first
|
||||
db, err := sql.Open("pgx", pg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec("DROP SCHEMA public CASCADE; CREATE SCHEMA public"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db.Close()
|
||||
dsn = pg
|
||||
}
|
||||
st, err := store.Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -30,10 +50,10 @@ func newSite(t *testing.T) (*httptest.Server, config.Config) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mux := api.New(st, cfg)
|
||||
mux.Handle("/", web.Handler())
|
||||
mux.Handle("/", web.Handler(manualPDF))
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv, cfg
|
||||
return srv, cfg, st
|
||||
}
|
||||
|
||||
func get(t *testing.T, url string) (*http.Response, string) {
|
||||
@@ -48,7 +68,7 @@ func get(t *testing.T, url string) (*http.Response, string) {
|
||||
}
|
||||
|
||||
func TestInfoAndAssemble(t *testing.T) {
|
||||
srv, cfg := newSite(t)
|
||||
srv, cfg := newSite(t, "")
|
||||
|
||||
_, body := get(t, srv.URL+"/info")
|
||||
var info struct {
|
||||
@@ -92,10 +112,10 @@ func TestInfoAndAssemble(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebAssets(t *testing.T) {
|
||||
srv, _ := newSite(t)
|
||||
srv, _ := newSite(t, "")
|
||||
for path, want := range map[string]string{
|
||||
"/": "<title>",
|
||||
"/manual.txt": "PROGRAMMER'S REFERENCE MANUAL",
|
||||
"/manual.md": "Programmer's Reference Manual",
|
||||
"/examples/echo.s": "ECHO",
|
||||
"/examples/index.json": `"pursue"`,
|
||||
} {
|
||||
@@ -108,8 +128,57 @@ func TestWebAssets(t *testing.T) {
|
||||
if !strings.Contains(resp.Header.Get("Content-Security-Policy"), "default-src 'self'") {
|
||||
t.Error("missing CSP header")
|
||||
}
|
||||
// The PDF is rendered separately; until it exists /manual.pdf explains how.
|
||||
presp, pbody := get(t, srv.URL+"/manual.pdf")
|
||||
if presp.StatusCode != 404 || !strings.Contains(pbody, "render-manual.sh") {
|
||||
t.Errorf("/manual.pdf before any render: status %d: %s", presp.StatusCode, pbody)
|
||||
}
|
||||
// The API still wins over the catch-all.
|
||||
if resp, _ := get(t, srv.URL+"/market"); !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||
t.Errorf("/market served as %q", resp.Header.Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualPDFServedFromFileAndPickedUpLive(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "manual.pdf")
|
||||
srv, _ := newSite(t, path)
|
||||
|
||||
if resp, _ := get(t, srv.URL+"/manual.pdf"); resp.StatusCode != 404 {
|
||||
t.Fatalf("before the file exists: %d", resp.StatusCode)
|
||||
}
|
||||
write := func(content string) {
|
||||
// Like the renderer: write beside the target, then rename into place.
|
||||
if err := os.WriteFile(path+".tmp", []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Rename(path+".tmp", path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
write("%PDF-1.7 first render")
|
||||
resp, body := get(t, srv.URL+"/manual.pdf")
|
||||
if resp.StatusCode != 200 || body != "%PDF-1.7 first render" || resp.Header.Get("Content-Type") != "application/pdf" {
|
||||
t.Fatalf("first render: %d %q %q", resp.StatusCode, body, resp.Header.Get("Content-Type"))
|
||||
}
|
||||
if resp.Header.Get("Cache-Control") != "no-cache" || resp.Header.Get("Content-Security-Policy") != "" {
|
||||
t.Errorf("headers: cache=%q csp=%q", resp.Header.Get("Cache-Control"), resp.Header.Get("Content-Security-Policy"))
|
||||
}
|
||||
|
||||
// A re-render is served immediately, with no restart.
|
||||
write("%PDF-1.7 second render, longer")
|
||||
if _, body := get(t, srv.URL+"/manual.pdf"); body != "%PDF-1.7 second render, longer" {
|
||||
t.Fatalf("new render not picked up: %q", body)
|
||||
}
|
||||
|
||||
// Range requests work (PDF viewers rely on them).
|
||||
req, _ := http.NewRequest("GET", srv.URL+"/manual.pdf", nil)
|
||||
req.Header.Set("Range", "bytes=0-4")
|
||||
rr, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rr.Body.Close()
|
||||
if part, _ := io.ReadAll(rr.Body); rr.StatusCode != 206 || string(part) != "%PDF-" {
|
||||
t.Fatalf("range: %d %q", rr.StatusCode, part)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user