// Copyright (c) 2025 Probo Inc . // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. package docgen import ( "html/template" "strings" "testing" "time" "github.com/getprobo/probo/pkg/coredata" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRenderHTML(t *testing.T) { now := time.Now() tests := []struct { name string data DocumentData wantContains []string wantNotContains []string }{ { name: "basic document with all fields", data: DocumentData{ Title: "Test Document", Content: "# Main Title\n\nThis is **bold** text with *italic* formatting.", Version: 1, Classification: ClassificationPublic, Approver: "John Doe", Description: "Test document description", PublishedAt: &now, Signatures: []SignatureData{ { SignedBy: "Alice Smith", SignedAt: &now, State: coredata.DocumentVersionSignatureStateSigned, RequestedAt: now, RequestedBy: "Bob Johnson", }, }, }, wantContains: []string{ "Test Document", "

Main Title

", "bold", "italic", "1", "PUBLIC", "John Doe", "Test document description", "Alice Smith", "Bob Johnson", }, }, { name: "document with HTML characters that need escaping", data: DocumentData{ Title: "Test & Doe", Description: "Description with & symbols and ", Signatures: []SignatureData{ { SignedBy: "Alice & ", RequestedBy: "Carol ", "Test & ", Approver: "User & ", Description: "Text with 'quotes' & \"double quotes\"", Signatures: []SignatureData{ { SignedBy: "tag", RequestedBy: "User & Company", State: coredata.DocumentVersionSignatureStateRequested, }, }, } result, err := RenderHTML(dangerousData) require.NoError(t, err) resultStr := string(result) // Verify dangerous content is escaped assert.NotContains(t, resultStr, "") assert.NotContains(t, resultStr, "tag") assert.Contains(t, resultStr, "<script>") assert.Contains(t, resultStr, "&") assert.Contains(t, resultStr, "'") } func TestMarkdownRendering(t *testing.T) { tests := []struct { name string markdown string want []string }{ { name: "headers", markdown: "# H1\n## H2\n### H3", want: []string{"

H1

", "

H2

", "

H3

"}, }, { name: "emphasis", markdown: "**bold** and *italic*", want: []string{"bold", "italic"}, }, { name: "lists", markdown: "- Item 1\n- Item 2", want: []string{"
    ", "
  • Item 1
  • ", "
  • Item 2
  • ", "
"}, }, { name: "paragraphs", markdown: "Paragraph 1\n\nParagraph 2", want: []string{"

Paragraph 1

", "

Paragraph 2

"}, }, { name: "code", markdown: "`inline code` and\n```\ncode block\n```", want: []string{"inline code", "
code block"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := DocumentData{
				Title:   "Markdown Test",
				Content: tt.markdown,
			}

			result, err := RenderHTML(data)
			require.NoError(t, err)

			resultStr := string(result)
			for _, want := range tt.want {
				assert.Contains(t, resultStr, want)
			}
		})
	}
}

func TestDocumentVersionSignatureStates(t *testing.T) {
	now := time.Now()

	states := []coredata.DocumentVersionSignatureState{
		coredata.DocumentVersionSignatureStateRequested,
		coredata.DocumentVersionSignatureStateSigned,
		// Add other states if they exist
	}

	for _, state := range states {
		t.Run(string(state), func(t *testing.T) {
			data := DocumentData{
				Title: "State Test",
				Signatures: []SignatureData{
					{
						SignedBy:    "Test User",
						State:       state,
						RequestedAt: now,
						RequestedBy: "Requester",
					},
				},
			}

			result, err := RenderHTML(data)
			assert.NoError(t, err)
			assert.NotEmpty(t, result)
		})
	}
}

func TestLargeContent(t *testing.T) {
	// Create a large markdown content
	var largeContent strings.Builder
	for i := 0; i < 1000; i++ {
		largeContent.WriteString("# Section ")
		largeContent.WriteString(string(rune('A' + i%26)))
		largeContent.WriteString("\n\nThis is a paragraph with **bold** and *italic* text.\n\n")
		largeContent.WriteString("- List item 1\n- List item 2\n- List item 3\n\n")
	}

	data := DocumentData{
		Title:   "Large Document",
		Content: largeContent.String(),
	}

	result, err := RenderHTML(data)
	assert.NoError(t, err)
	assert.NotEmpty(t, result)
	assert.True(t, len(result) > 10000) // Should be reasonably large
}

func BenchmarkGenerateHTML(b *testing.B) {
	now := time.Now()

	data := DocumentData{
		Title:          "Benchmark Document",
		Content:        "# Title\n\nThis is **bold** text with *italic* formatting.\n\n- Item 1\n- Item 2",
		Version:        1,
		Classification: ClassificationPublic,
		Approver:       "John Doe",
		Description:    "Benchmark test document",
		PublishedAt:    &now,
		Signatures: []SignatureData{
			{
				SignedBy:    "Alice Smith",
				SignedAt:    &now,
				State:       coredata.DocumentVersionSignatureStateSigned,
				RequestedAt: now,
				RequestedBy: "Bob Johnson",
			},
		},
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := RenderHTML(data)
		if err != nil {
			b.Fatal(err)
		}
	}
}