Files

462 lines
11 KiB
Go

package pdfrender
import (
"bamort/config"
"bamort/database"
"bamort/models"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
)
func init() {
// Set templates directory for tests (tests run from pdfrender/ directory)
config.Cfg.TemplatesDir = "../templates"
config.Cfg.ExportTempDir = "../xporttemp"
}
func TestListTemplates(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/templates", ListTemplates)
// Act
req, _ := http.NewRequest("GET", "/templates", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
var response []TemplateInfo
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to parse response: %v", err)
}
// Should return at least one template
if len(response) == 0 {
t.Error("Expected at least one template in response")
}
// Verify structure of first template
if len(response) > 0 {
tmpl := response[0]
if tmpl.ID == "" {
t.Error("Template ID should not be empty")
}
if tmpl.Name == "" {
t.Error("Template Name should not be empty")
}
}
}
func TestExportCharacterToPDF(t *testing.T) {
// Arrange
database.SetupTestDB()
// Use test-specific temp directory
testDir := "../xporttemp_test_basic"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
// Load test character
char := &models.Char{}
err := char.FirstID("18")
if err != nil {
t.Fatalf("Failed to load test character: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/export/:id", ExportCharacterToPDF)
// Act - Export with default template
req, _ := http.NewRequest("GET", "/export/18", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
}
// Check content type - now returns JSON
contentType := w.Header().Get("Content-Type")
if !strings.Contains(contentType, "application/json") {
t.Errorf("Expected Content-Type 'application/json', got '%s'", contentType)
}
// Parse JSON response
var response map[string]string
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
// Verify filename is returned
filename, ok := response["filename"]
if !ok {
t.Fatal("Response should contain 'filename' field")
}
if filename == "" {
t.Error("Filename should not be empty")
}
// Verify PDF file exists
filePath := filepath.Join(testDir, filename)
data, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("Failed to read PDF file: %v", err)
}
// Verify PDF marker
if string(data[0:4]) != "%PDF" {
t.Error("File does not start with PDF marker")
}
}
func TestExportCharacterToPDF_WithTemplate(t *testing.T) {
// Arrange
database.SetupTestDB()
// Use test-specific temp directory
testDir := "../xporttemp_test_template"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/export/:id", ExportCharacterToPDF)
// Act - Export with specific template
req, _ := http.NewRequest("GET", "/export/18?template=Default_A4_Quer", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
}
// Parse JSON response
var response map[string]string
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
filename := response["filename"]
filePath := filepath.Join(testDir, filename)
// Verify it's a PDF
data, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("Failed to read PDF: %v", err)
}
if string(data[0:4]) != "%PDF" {
t.Error("File does not start with PDF marker")
}
}
func TestExportCharacterToPDF_CharacterNotFound(t *testing.T) {
// Arrange
database.SetupTestDB()
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/export/:id", ExportCharacterToPDF)
// Act - Try to export non-existent character
req, _ := http.NewRequest("GET", "/export/99999", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", w.Code)
}
}
func TestExportCharacterToPDF_SavesFileAndReturnsFilename(t *testing.T) {
// Arrange
database.SetupTestDB()
// Use test-specific temp directory
testDir := "../xporttemp_test"
config.Cfg.ExportTempDir = testDir
defer func() {
// Cleanup
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
// Load test character
char := &models.Char{}
err := char.FirstID("18")
if err != nil {
t.Fatalf("Failed to load test character: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/export/:id", ExportCharacterToPDF)
// Act
req, _ := http.NewRequest("GET", "/export/18", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
}
// Parse response
var response map[string]string
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to parse JSON response: %v", err)
}
// Check that filename is returned
filename, ok := response["filename"]
if !ok {
t.Fatal("Response should contain 'filename' field")
}
if filename == "" {
t.Error("Filename should not be empty")
}
// Verify filename format (should contain character name and timestamp)
if !strings.Contains(filename, "Fanjo_Vetrani") {
t.Errorf("Filename should contain sanitized character name, got: %s", filename)
}
if !strings.HasSuffix(filename, ".pdf") {
t.Errorf("Filename should end with .pdf, got: %s", filename)
}
// Verify file exists in xporttemp directory
filePath := filepath.Join(testDir, filename)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
t.Errorf("PDF file should exist at %s", filePath)
}
// Verify it's a valid PDF
data, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("Failed to read PDF file: %v", err)
}
if len(data) == 0 {
t.Error("PDF file should not be empty")
}
if string(data[0:4]) != "%PDF" {
t.Error("File should be a valid PDF")
}
}
func TestGetPDFFile(t *testing.T) {
setupTestEnvironment(t)
// Create test directory and file
testDir := "../xporttemp_test_get"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
if err := os.MkdirAll(testDir, 0755); err != nil {
t.Fatalf("Failed to create test directory: %v", err)
}
// Create a test PDF file
testFilename := "Test_Character_20231225_120000.pdf"
testPDFContent := []byte("%PDF-1.4\nTest PDF Content")
filePath := filepath.Join(testDir, testFilename)
if err := os.WriteFile(filePath, testPDFContent, 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/file/:filename", GetPDFFile)
// Act - Get the PDF file
req, _ := http.NewRequest("GET", "/file/"+testFilename, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
}
// Check content type
contentType := w.Header().Get("Content-Type")
if contentType != "application/pdf" {
t.Errorf("Expected Content-Type 'application/pdf', got '%s'", contentType)
}
// Check content
body := w.Body.Bytes()
if string(body) != string(testPDFContent) {
t.Errorf("Expected PDF content, got different content")
}
}
func TestGetPDFFile_NotFound(t *testing.T) {
setupTestEnvironment(t)
testDir := "../xporttemp_test_notfound"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
if err := os.MkdirAll(testDir, 0755); err != nil {
t.Fatalf("Failed to create test directory: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/file/:filename", GetPDFFile)
// Act - Try to get non-existent file
req, _ := http.NewRequest("GET", "/file/nonexistent.pdf", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", w.Code)
}
}
func TestGetPDFFile_PathTraversal(t *testing.T) {
setupTestEnvironment(t)
testDir := "../xporttemp_test_security"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
if err := os.MkdirAll(testDir, 0755); err != nil {
t.Fatalf("Failed to create test directory: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/file/:filename", GetPDFFile)
// Act - Try path traversal attack
req, _ := http.NewRequest("GET", "/file/../../../etc/passwd", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert - Should return error (either 404 or 400)
if w.Code == http.StatusOK {
t.Error("Should not allow path traversal attacks")
}
}
func TestCleanupEndpoint(t *testing.T) {
setupTestEnvironment(t)
// Create test directory with old and new files
testDir := "../xporttemp_test_cleanup_endpoint"
config.Cfg.ExportTempDir = testDir
defer func() {
os.RemoveAll(testDir)
config.Cfg.ExportTempDir = "../xporttemp"
}()
if err := os.MkdirAll(testDir, 0755); err != nil {
t.Fatalf("Failed to create test directory: %v", err)
}
now := time.Now()
// Create old file (8 days old)
oldFile := filepath.Join(testDir, "old_file.pdf")
if err := os.WriteFile(oldFile, []byte("%PDF-old"), 0644); err != nil {
t.Fatalf("Failed to create old file: %v", err)
}
oldTime := now.Add(-8 * 24 * time.Hour)
if err := os.Chtimes(oldFile, oldTime, oldTime); err != nil {
t.Fatalf("Failed to set old file time: %v", err)
}
// Create recent file (3 days old)
recentFile := filepath.Join(testDir, "recent_file.pdf")
if err := os.WriteFile(recentFile, []byte("%PDF-recent"), 0644); err != nil {
t.Fatalf("Failed to create recent file: %v", err)
}
recentTime := now.Add(-3 * 24 * time.Hour)
if err := os.Chtimes(recentFile, recentTime, recentTime); err != nil {
t.Fatalf("Failed to set recent file time: %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/cleanup", CleanupExportTemp)
// Act
req, _ := http.NewRequest("POST", "/cleanup", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
}
// Parse response
var response map[string]interface{}
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
// Check deleted count
deletedCount, ok := response["deleted"]
if !ok {
t.Fatal("Response should contain 'deleted' field")
}
// Should have deleted 1 file (the old one)
if int(deletedCount.(float64)) != 1 {
t.Errorf("Expected 1 file deleted, got %v", deletedCount)
}
// Verify old file is deleted
if _, err := os.Stat(oldFile); !os.IsNotExist(err) {
t.Error("Old file should be deleted")
}
// Verify recent file still exists
if _, err := os.Stat(recentFile); err != nil {
t.Error("Recent file should still exist")
}
}