package api import ( "bytes" "context" "fmt" "os/exec" "path/filepath" "testing" "time" ) func fixtureRenderer(t *testing.T) *pageRenderer { t.Helper() node, err := exec.LookPath("node") if err != nil { t.Skip("Node.js unavailable") } script, err := filepath.Abs("testdata/render-fixture.mjs") if err != nil { t.Fatal(err) } return &pageRenderer{node: node, script: script, slots: make(chan struct{}, 2), cache: map[string]renderCacheEntry{}} } func TestRendererRefreshesContent(t *testing.T) { p := fixtureRenderer(t) for _, step := range []struct { text string hit bool }{{"old", false}, {"old", true}, {"new", false}, {"new", true}} { out, hit, err := p.render(context.Background(), "/page/", []byte(fmt.Sprintf(`{"text":%q}`, step.text))) if err != nil || hit != step.hit || !bytes.Contains(out, []byte(step.text)) { t.Fatalf("step %+v: hit=%v err=%v", step, hit, err) } } if p.cacheBytes != len(p.cache["/page/"].html) { t.Fatal("replacement cache accounting is wrong") } } func TestRendererRejectsBrokenOutput(t *testing.T) { p := fixtureRenderer(t) for _, input := range []string{`{"fail":true}`, `{"invalid":true}`} { if _, _, err := p.render(context.Background(), "/page/", []byte(input)); err == nil { t.Fatal("broken output accepted") } } if len(p.cache) != 0 || len(p.slots) != 0 { t.Fatal("failure cached or renderer slot leaked") } ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() if _, _, err := p.render(ctx, "/page/", []byte(`{"wait":5000}`)); err == nil { t.Fatal("cancelled render succeeded") } if len(p.slots) != 0 { t.Fatal("cancelled render leaked slot") } } func TestRendererDoesNotCacheQueryVariants(t *testing.T) { p := fixtureRenderer(t) for i := 0; i < 2; i++ { _, hit, err := p.render(context.Background(), "/page/?q=test", []byte(`{"text":"query"}`)) if err != nil || hit { t.Fatalf("query variant: hit=%v err=%v", hit, err) } } if len(p.cache) != 0 { t.Fatal("query displaced canonical cache") } } func TestCappedBuffer(t *testing.T) { b := &cappedBuffer{limit: 4} if n, err := b.Write([]byte("abcdef")); n != 6 || err != nil || b.String() != "abcd" || !b.overflow { t.Fatal("output bound failed") } if _, err := b.Write([]byte("more")); err != nil || b.Len() != 4 { t.Fatal("repeated overflow failed") } }