package main import ( "bytes" "encoding/json" "fmt" "html/template" "io/fs" "net/http" "net/http/httptest" "os" "os/exec" "testing" "time" "toolbox/pkg/base" "github.com/gin-gonic/gin" ) const ( testPort = "9999" baseURL = "http://localhost:" + testPort ) func TestOrbitViewerTemplatesAndRoutes(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() for id, tool := range base.Registry { if err := tool.Init(); err != nil { t.Fatalf("Failed to initialize tool %s: %v", id, err) } tool.RegisterRoutes(r.Group("/api/" + id)) } subFS, err := fs.Sub(webFS, "web") if err != nil { t.Fatalf("Failed to fs.Sub: %v", err) } tmpl, err := template.ParseFS(subFS, "layout.html", "index.html", "tools/*.html") if err != nil { t.Fatalf("Failed to parse templates: %v", err) } serveIndex := func(c *gin.Context) { data := gin.H{ "Title": "测试", "Description": "描述", "Keywords": "关键词", "CanonicalURL": "https://toolbox.pengzhan.dev/orbit-viewer", "GA_ID": "", } var buf bytes.Buffer if err := tmpl.ExecuteTemplate(&buf, "layout.html", data); err != nil { c.String(http.StatusInternalServerError, "Template error: %v", err) return } c.Data(http.StatusOK, "text/html; charset=utf-8", buf.Bytes()) } r.GET("/orbit-viewer", serveIndex) // Test GET /orbit-viewer req, _ := http.NewRequest("GET", "/orbit-viewer", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("Expected status 200 for /orbit-viewer, got %d: %s", w.Code, w.Body.String()) } bodyStr := w.Body.String() if !bytes.Contains([]byte(bodyStr), []byte("panel-orbit-viewer")) { t.Errorf("Expected body to contain panel-orbit-viewer") } if !bytes.Contains([]byte(bodyStr), []byte("阶段一:卫星轨道与全球投影轨迹")) { t.Errorf("Expected body to contain orbit viewer content") } } func TestToolboxE2E(t *testing.T) { // 1. Build the binary fmt.Println("--- Step 1: Building binary... ---") _ = os.MkdirAll("build", 0755) buildPath := "build/toolbox_test_bin" buildCmd := exec.Command("go", "build", "-o", buildPath, "main.go") if err := buildCmd.Run(); err != nil { t.Fatalf("Failed to build binary: %v", err) } defer func() { _ = os.Remove(buildPath) }() // 2. Start the server fmt.Println("--- Step 2: Starting server... ---") serverCmd := exec.Command("./"+buildPath, "-port", testPort) if err := serverCmd.Start(); err != nil { t.Fatalf("Failed to start server: %v", err) } defer func() { if serverCmd.Process != nil { _ = serverCmd.Process.Kill() } }() // 3. Wait for server to be ready fmt.Println("--- Step 3: Waiting for server readiness... ---") ready := false for i := 0; i < 10; i++ { resp, err := http.Get(baseURL + "/api/tools") if err == nil && resp.StatusCode == http.StatusOK { ready = true _ = resp.Body.Close() break } time.Sleep(500 * time.Millisecond) } if !ready { t.Fatal("Server failed to become ready in time") } // 4. Test endpoints fmt.Println("--- Step 4: Testing tool endpoints... ---") testOutputDir, err := os.MkdirTemp("", "toolbox_test_output_*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } fmt.Printf("--- Test Artifacts will be saved to: %s ---\n", testOutputDir) tests := []struct { name string path string method string fileName string body interface{} }{ { name: "Zitie Teaching", path: "/api/zitie/teaching", method: "POST", fileName: "zitie_teaching.pdf", body: map[string]string{"chars": "永和九年", "paper_size": "A4"}, }, { name: "Zitie Step", path: "/api/zitie/step", method: "POST", fileName: "zitie_step.pdf", body: map[string]string{"chars": "永", "paper_size": "A4"}, }, { name: "Zitie Manuscript", path: "/api/zitie/manuscript", method: "POST", fileName: "zitie_manuscript.pdf", body: map[string]string{"chars": "天道酬勤", "paper_size": "A4", "font_type": "kaiti"}, }, { name: "Learn Number Counting", path: "/api/learn-number/counting", method: "POST", fileName: "counting.pdf", body: map[string]interface{}{"page_count": 1, "paper_size": "A4"}, }, { name: "Learn Number Writing", path: "/api/learn-number/writing", method: "POST", fileName: "writing.pdf", body: map[string]interface{}{"start_num": 1, "end_num": 5, "paper_size": "A4"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var resp *http.Response var err error if tt.method == "POST" { b, _ := json.Marshal(tt.body) resp, err = http.Post(baseURL+tt.path, "application/json", bytes.NewBuffer(b)) } else { resp, err = http.Get(baseURL + tt.path) } if err != nil { t.Errorf("%s failed: %v", tt.name, err) return } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { t.Errorf("%s returned status %d", tt.name, resp.StatusCode) return } contentType := resp.Header.Get("Content-Type") if contentType != "application/pdf" { t.Errorf("%s expected application/pdf, got %s", tt.name, contentType) } else { // Save PDF to temp directory for human review outPath := testOutputDir + "/" + tt.fileName f, err := os.Create(outPath) if err != nil { t.Errorf("Failed to create output file: %v", err) return } defer func() { _ = f.Close() }() _, err = f.ReadFrom(resp.Body) if err != nil { t.Errorf("Failed to save PDF: %v", err) return } fmt.Printf("[PASS] %s: Saved to %s\n", tt.name, outPath) } }) } fmt.Printf("\n--- ALL TESTS DONE. PLEASE REVIEW PDFS IN: %s ---\n", testOutputDir) }