open-code-review/internal/viewer/server_extra_test.go
kite e2d75b732a
Some checks are pending
CI / test (push) Waiting to run
test: fix golangci-lint errcheck/staticcheck issues in test code (#323)
2026-07-08 22:46:18 +08:00

238 lines
6 KiB
Go

package viewer
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestParseTemplate_ReposHTML(t *testing.T) {
tmpl, err := parseTemplate("repos.html")
if err != nil {
t.Fatalf("parseTemplate(repos.html) error: %v", err)
}
if tmpl == nil {
t.Fatal("parseTemplate returned nil template")
}
}
func TestParseTemplate_SessionsHTML(t *testing.T) {
tmpl, err := parseTemplate("sessions.html")
if err != nil {
t.Fatalf("parseTemplate(sessions.html) error: %v", err)
}
if tmpl == nil {
t.Fatal("parseTemplate returned nil template")
}
}
func TestParseTemplate_SessionHTML(t *testing.T) {
tmpl, err := parseTemplate("session.html")
if err != nil {
t.Fatalf("parseTemplate(session.html) error: %v", err)
}
if tmpl == nil {
t.Fatal("parseTemplate returned nil template")
}
}
func TestParseTemplate_NonExistent(t *testing.T) {
_, err := parseTemplate("nonexistent.html")
if err == nil {
t.Error("expected error for non-existent template")
}
}
func TestRenderTemplate_Success(t *testing.T) {
rr := httptest.NewRecorder()
renderTemplate(rr, "repos.html", map[string]any{
"Repos": []RepoInfo{},
})
if rr.Code != http.StatusOK {
t.Errorf("status = %d, want 200", rr.Code)
}
ct := rr.Header().Get("Content-Type")
if ct != "text/html; charset=utf-8" {
t.Errorf("Content-Type = %q", ct)
}
if !strings.Contains(rr.Body.String(), "No session data found") {
t.Errorf("expected empty repos message in rendered output")
}
}
func TestRenderTemplate_WithRepos(t *testing.T) {
rr := httptest.NewRecorder()
renderTemplate(rr, "repos.html", map[string]any{
"Repos": []RepoInfo{
{EncodedPath: "my-project", SessionCount: 3},
},
})
if rr.Code != http.StatusOK {
t.Errorf("status = %d, want 200", rr.Code)
}
if !strings.Contains(rr.Body.String(), "my-project") {
t.Errorf("expected repo name in rendered output")
}
}
func TestRenderTemplate_BadTemplate(t *testing.T) {
rr := httptest.NewRecorder()
renderTemplate(rr, "nonexistent.html", nil)
if rr.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want 500", rr.Code)
}
if !strings.Contains(rr.Body.String(), "template error") {
t.Errorf("expected template error message")
}
}
func TestRenderTemplate_Sessions(t *testing.T) {
rr := httptest.NewRecorder()
renderTemplate(rr, "sessions.html", sessionsData{
EncodedRepo: "test-repo",
RepoName: "MyProject",
Sessions: []SessionSummary{},
})
if rr.Code != http.StatusOK {
t.Errorf("status = %d, want 200", rr.Code)
}
if !strings.Contains(rr.Body.String(), "MyProject") {
t.Errorf("expected repo name in sessions template")
}
}
func TestRenderTemplate_SessionPage(t *testing.T) {
rr := httptest.NewRecorder()
vs := &ViewSession{
Summary: SessionSummary{
SessionID: "abc",
Model: "gpt-4",
CWD: "/test",
},
Files: []*FileGroup{
{
FilePath: "main.go",
Tasks: map[TaskType][]*TaskCard{
MainTask: {
{
RequestNo: 1,
ResponseContent: "looks good",
Model: "gpt-4",
PromptTokens: 100,
CompletionTokens: 50,
},
},
},
},
},
}
renderTemplate(rr, "session.html", sessionPageData{
EncodedRepo: "repo",
RepoName: "MyRepo",
Session: vs,
})
if rr.Code != http.StatusOK {
t.Errorf("status = %d, want 200", rr.Code)
}
}
func TestRenderTemplate_ExecutionError(t *testing.T) {
rr := httptest.NewRecorder()
// Pass wrong data type to trigger template execution error
// repos.html expects .Repos to be rangeable; passing a string causes execution error
renderTemplate(rr, "repos.html", map[string]any{
"Repos": "not-a-slice",
})
// Template execution may partially write before failing, so we just check it didn't panic
// and that something was written (the header was set before Execute)
ct := rr.Header().Get("Content-Type")
if ct != "text/html; charset=utf-8" {
t.Errorf("Content-Type = %q", ct)
}
}
func TestStaticFS(t *testing.T) {
sfs := staticFS()
if sfs == nil {
t.Fatal("staticFS() returned nil")
}
// Should be able to open style.css
f, err := sfs.Open("style.css")
if err != nil {
t.Fatalf("failed to open style.css from staticFS: %v", err)
}
_ = f.Close()
}
func TestResolveAllowedHostsFromEnv(t *testing.T) {
t.Setenv(EnvAllowedHosts, "custom.host,other.host")
allowed := resolveAllowedHostsFromEnv("192.168.1.5:5483")
if _, ok := allowed["localhost"]; !ok {
t.Error("missing localhost")
}
if _, ok := allowed["192.168.1.5"]; !ok {
t.Error("missing bind host")
}
if _, ok := allowed["custom.host"]; !ok {
t.Error("missing custom.host from env")
}
if _, ok := allowed["other.host"]; !ok {
t.Error("missing other.host from env")
}
}
func TestBuildAllowedHosts_BracketedIPv6(t *testing.T) {
a := buildAllowedHosts("[fe80::1]", "")
if _, ok := a["fe80::1"]; !ok {
t.Errorf("bracketed IPv6 bind host not stripped: %v", a)
}
}
func TestResolveAllowedHostsFromEnv_NoEnv(t *testing.T) {
t.Setenv(EnvAllowedHosts, "")
allowed := resolveAllowedHostsFromEnv(":5483")
if len(allowed) != 3 {
t.Errorf("expected 3 default hosts, got %d: %v", len(allowed), allowed)
}
}
func TestTemplateFuncTaskTypeClass(t *testing.T) {
tmpl, err := parseTemplate("session.html")
if err != nil {
t.Fatal(err)
}
// Verify we can execute with task data that exercises taskTypeClass
rr := httptest.NewRecorder()
vs := &ViewSession{
Summary: SessionSummary{SessionID: "x", CWD: "/p"},
Files: []*FileGroup{
{
FilePath: "f.go",
Tasks: map[TaskType][]*TaskCard{
PlanTask: {{RequestNo: 1, ResponseContent: "plan"}},
MainTask: {{RequestNo: 2, ResponseContent: "main"}},
MemoryCompressionTask: {{RequestNo: 3, ResponseContent: "mem"}},
ReLocationTask: {{RequestNo: 4, ResponseContent: "reloc"}},
TaskType("custom"): {{RequestNo: 5, ResponseContent: "custom"}},
},
},
},
}
err = tmpl.Execute(rr, sessionPageData{
EncodedRepo: "r",
RepoName: "R",
Session: vs,
})
if err != nil {
t.Errorf("template execution with all task types: %v", err)
}
}