This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-service/internal/services/email_template_service_test.go
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

699 lines
23 KiB
Go

package services
import (
"regexp"
"strings"
"testing"
"github.com/breakpilot/consent-service/internal/models"
)
// ========================================
// Test All 19 Email Categories
// ========================================
// TestEmailTemplateService_GetDefaultTemplateContent tests default content generation for each email type
func TestEmailTemplateService_GetDefaultTemplateContent(t *testing.T) {
service := &EmailTemplateService{}
// All 19 email categories
tests := []struct {
name string
emailType string
language string
wantSubject bool
wantBodyHTML bool
wantBodyText bool
}{
// Auth Lifecycle (10 types)
{"welcome_de", models.EmailTypeWelcome, "de", true, true, true},
{"email_verification_de", models.EmailTypeEmailVerification, "de", true, true, true},
{"password_reset_de", models.EmailTypePasswordReset, "de", true, true, true},
{"password_changed_de", models.EmailTypePasswordChanged, "de", true, true, true},
{"2fa_enabled_de", models.EmailType2FAEnabled, "de", true, true, true},
{"2fa_disabled_de", models.EmailType2FADisabled, "de", true, true, true},
{"new_device_login_de", models.EmailTypeNewDeviceLogin, "de", true, true, true},
{"suspicious_activity_de", models.EmailTypeSuspiciousActivity, "de", true, true, true},
{"account_locked_de", models.EmailTypeAccountLocked, "de", true, true, true},
{"account_unlocked_de", models.EmailTypeAccountUnlocked, "de", true, true, true},
// GDPR/Privacy (5 types)
{"deletion_requested_de", models.EmailTypeDeletionRequested, "de", true, true, true},
{"deletion_confirmed_de", models.EmailTypeDeletionConfirmed, "de", true, true, true},
{"data_export_ready_de", models.EmailTypeDataExportReady, "de", true, true, true},
{"email_changed_de", models.EmailTypeEmailChanged, "de", true, true, true},
{"email_change_verify_de", models.EmailTypeEmailChangeVerify, "de", true, true, true},
// Consent Management (4 types)
{"new_version_published_de", models.EmailTypeNewVersionPublished, "de", true, true, true},
{"consent_reminder_de", models.EmailTypeConsentReminder, "de", true, true, true},
{"consent_deadline_warning_de", models.EmailTypeConsentDeadlineWarning, "de", true, true, true},
{"account_suspended_de", models.EmailTypeAccountSuspended, "de", true, true, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
subject, bodyHTML, bodyText := service.GetDefaultTemplateContent(tt.emailType, tt.language)
if tt.wantSubject && subject == "" {
t.Errorf("GetDefaultTemplateContent(%s, %s): expected subject, got empty string", tt.emailType, tt.language)
}
if tt.wantBodyHTML && bodyHTML == "" {
t.Errorf("GetDefaultTemplateContent(%s, %s): expected bodyHTML, got empty string", tt.emailType, tt.language)
}
if tt.wantBodyText && bodyText == "" {
t.Errorf("GetDefaultTemplateContent(%s, %s): expected bodyText, got empty string", tt.emailType, tt.language)
}
})
}
}
// TestEmailTemplateService_GetDefaultTemplateContent_UnknownType tests default content for unknown type
func TestEmailTemplateService_GetDefaultTemplateContent_UnknownType(t *testing.T) {
service := &EmailTemplateService{}
subject, bodyHTML, bodyText := service.GetDefaultTemplateContent("unknown_type", "de")
// The service returns a fallback for unknown types
if subject == "" {
t.Errorf("GetDefaultTemplateContent(unknown_type, de): expected fallback subject, got empty")
}
if bodyHTML == "" {
t.Errorf("GetDefaultTemplateContent(unknown_type, de): expected fallback bodyHTML, got empty")
}
if bodyText == "" {
t.Errorf("GetDefaultTemplateContent(unknown_type, de): expected fallback bodyText, got empty")
}
}
// TestEmailTemplateService_GetDefaultTemplateContent_UnsupportedLanguage tests fallback for unsupported language
func TestEmailTemplateService_GetDefaultTemplateContent_UnsupportedLanguage(t *testing.T) {
service := &EmailTemplateService{}
// Test with unsupported language - should return fallback
subject, bodyHTML, bodyText := service.GetDefaultTemplateContent(models.EmailTypeWelcome, "fr")
// Should return fallback (not empty, but generic)
if subject == "" || bodyHTML == "" || bodyText == "" {
t.Error("GetDefaultTemplateContent should return fallback for unsupported language")
}
}
// TestReplaceVariables tests variable replacement in templates
func TestReplaceVariables(t *testing.T) {
tests := []struct {
name string
template string
variables map[string]string
expected string
}{
{
name: "single variable",
template: "Hallo {{user_name}}!",
variables: map[string]string{"user_name": "Max"},
expected: "Hallo Max!",
},
{
name: "multiple variables",
template: "Hallo {{user_name}}, klicken Sie hier: {{reset_link}}",
variables: map[string]string{"user_name": "Max", "reset_link": "https://example.com"},
expected: "Hallo Max, klicken Sie hier: https://example.com",
},
{
name: "no variables",
template: "Hallo Welt!",
variables: map[string]string{},
expected: "Hallo Welt!",
},
{
name: "missing variable - not replaced",
template: "Hallo {{user_name}} und {{missing}}!",
variables: map[string]string{"user_name": "Max"},
expected: "Hallo Max und {{missing}}!",
},
{
name: "empty template",
template: "",
variables: map[string]string{"user_name": "Max"},
expected: "",
},
{
name: "variable with special characters",
template: "IP: {{ip_address}}",
variables: map[string]string{"ip_address": "192.168.1.1"},
expected: "IP: 192.168.1.1",
},
{
name: "variable with URL",
template: "Link: {{verification_url}}",
variables: map[string]string{"verification_url": "https://example.com/verify?token=abc123&user=test"},
expected: "Link: https://example.com/verify?token=abc123&user=test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := testReplaceVariables(tt.template, tt.variables)
if result != tt.expected {
t.Errorf("replaceVariables() = %s, want %s", result, tt.expected)
}
})
}
}
// testReplaceVariables is a test helper function for variable replacement
func testReplaceVariables(template string, variables map[string]string) string {
result := template
for key, value := range variables {
placeholder := "{{" + key + "}}"
for i := 0; i < len(result); i++ {
idx := testFindSubstring(result, placeholder)
if idx == -1 {
break
}
result = result[:idx] + value + result[idx+len(placeholder):]
}
}
return result
}
func testFindSubstring(s, substr string) int {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return i
}
}
return -1
}
// TestEmailTypeConstantsExist verifies that all expected email types are defined
func TestEmailTypeConstantsExist(t *testing.T) {
// Test that all 19 email type constants are defined and produce non-empty templates
types := []string{
// Auth Lifecycle
models.EmailTypeWelcome,
models.EmailTypeEmailVerification,
models.EmailTypePasswordReset,
models.EmailTypePasswordChanged,
models.EmailType2FAEnabled,
models.EmailType2FADisabled,
models.EmailTypeNewDeviceLogin,
models.EmailTypeSuspiciousActivity,
models.EmailTypeAccountLocked,
models.EmailTypeAccountUnlocked,
// GDPR/Privacy
models.EmailTypeDeletionRequested,
models.EmailTypeDeletionConfirmed,
models.EmailTypeDataExportReady,
models.EmailTypeEmailChanged,
models.EmailTypeEmailChangeVerify,
// Consent Management
models.EmailTypeNewVersionPublished,
models.EmailTypeConsentReminder,
models.EmailTypeConsentDeadlineWarning,
models.EmailTypeAccountSuspended,
}
service := &EmailTemplateService{}
for _, emailType := range types {
t.Run(emailType, func(t *testing.T) {
subject, bodyHTML, _ := service.GetDefaultTemplateContent(emailType, "de")
if subject == "" {
t.Errorf("Email type %s has no default subject", emailType)
}
if bodyHTML == "" {
t.Errorf("Email type %s has no default body HTML", emailType)
}
})
}
// Verify we have exactly 19 types
if len(types) != 19 {
t.Errorf("Expected 19 email types, got %d", len(types))
}
}
// TestEmailTemplateService_ValidateTemplateContent tests template content validation
func TestEmailTemplateService_ValidateTemplateContent(t *testing.T) {
tests := []struct {
name string
subject string
bodyHTML string
wantError bool
}{
{
name: "valid content",
subject: "Test Subject",
bodyHTML: "<p>Test Body</p>",
wantError: false,
},
{
name: "empty subject",
subject: "",
bodyHTML: "<p>Test Body</p>",
wantError: true,
},
{
name: "empty body",
subject: "Test Subject",
bodyHTML: "",
wantError: true,
},
{
name: "both empty",
subject: "",
bodyHTML: "",
wantError: true,
},
{
name: "whitespace only subject",
subject: " ",
bodyHTML: "<p>Test Body</p>",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := testValidateTemplateContent(tt.subject, tt.bodyHTML)
if (err != nil) != tt.wantError {
t.Errorf("validateTemplateContent() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
// testValidateTemplateContent is a test helper function to validate template content
func testValidateTemplateContent(subject, bodyHTML string) error {
if strings.TrimSpace(subject) == "" {
return &templateValidationError{Field: "subject", Message: "subject is required"}
}
if strings.TrimSpace(bodyHTML) == "" {
return &templateValidationError{Field: "body_html", Message: "body_html is required"}
}
return nil
}
// templateValidationError represents a validation error in email templates
type templateValidationError struct {
Field string
Message string
}
func (e *templateValidationError) Error() string {
return e.Field + ": " + e.Message
}
// TestGetTestVariablesForType tests that test variables are properly generated for each email type
func TestGetTestVariablesForType(t *testing.T) {
tests := []struct {
emailType string
expectedVars []string
}{
// Auth Lifecycle
{models.EmailTypeWelcome, []string{"user_name", "app_name"}},
{models.EmailTypeEmailVerification, []string{"user_name", "verification_url"}},
{models.EmailTypePasswordReset, []string{"reset_url"}},
{models.EmailTypePasswordChanged, []string{"user_name", "changed_at"}},
{models.EmailType2FAEnabled, []string{"user_name", "enabled_at"}},
{models.EmailType2FADisabled, []string{"user_name", "disabled_at"}},
{models.EmailTypeNewDeviceLogin, []string{"device", "location", "ip_address", "login_time"}},
{models.EmailTypeSuspiciousActivity, []string{"activity_type", "activity_time"}},
{models.EmailTypeAccountLocked, []string{"locked_at", "reason"}},
{models.EmailTypeAccountUnlocked, []string{"unlocked_at"}},
// GDPR/Privacy
{models.EmailTypeDeletionRequested, []string{"deletion_date", "cancel_url"}},
{models.EmailTypeDeletionConfirmed, []string{"deleted_at"}},
{models.EmailTypeDataExportReady, []string{"download_url", "expires_in"}},
{models.EmailTypeEmailChanged, []string{"old_email", "new_email"}},
// Consent Management
{models.EmailTypeNewVersionPublished, []string{"document_name", "version"}},
{models.EmailTypeConsentReminder, []string{"document_name", "days_left"}},
{models.EmailTypeConsentDeadlineWarning, []string{"document_name", "hours_left"}},
{models.EmailTypeAccountSuspended, []string{"suspended_at", "reason"}},
}
for _, tt := range tests {
t.Run(tt.emailType, func(t *testing.T) {
vars := getTestVariablesForType(tt.emailType)
for _, expected := range tt.expectedVars {
if _, ok := vars[expected]; !ok {
t.Errorf("getTestVariablesForType(%s) missing variable %s", tt.emailType, expected)
}
}
})
}
}
// getTestVariablesForType returns test variables for a given email type
func getTestVariablesForType(emailType string) map[string]string {
// Common variables
vars := map[string]string{
"user_name": "Max Mustermann",
"user_email": "max@example.com",
"app_name": "BreakPilot",
"app_url": "https://breakpilot.app",
"support_url": "https://breakpilot.app/support",
"support_email": "support@breakpilot.app",
"security_url": "https://breakpilot.app/security",
"login_url": "https://breakpilot.app/login",
}
switch emailType {
case models.EmailTypeEmailVerification:
vars["verification_url"] = "https://breakpilot.app/verify?token=xyz789"
vars["verification_code"] = "ABC123"
vars["expires_in"] = "24 Stunden"
case models.EmailTypePasswordReset:
vars["reset_url"] = "https://breakpilot.app/reset?token=abc123"
vars["reset_code"] = "RST456"
vars["expires_in"] = "1 Stunde"
vars["ip_address"] = "192.168.1.1"
case models.EmailTypePasswordChanged:
vars["changed_at"] = "14.12.2025 15:30 Uhr"
vars["ip_address"] = "192.168.1.1"
vars["device_info"] = "Chrome auf MacOS"
case models.EmailType2FAEnabled:
vars["enabled_at"] = "14.12.2025 15:30 Uhr"
vars["device_info"] = "Chrome auf MacOS"
case models.EmailType2FADisabled:
vars["disabled_at"] = "14.12.2025 15:30 Uhr"
vars["ip_address"] = "192.168.1.1"
case models.EmailTypeNewDeviceLogin:
vars["device"] = "Chrome auf MacOS"
vars["device_info"] = "Chrome auf MacOS"
vars["location"] = "Berlin, Deutschland"
vars["ip_address"] = "192.168.1.1"
vars["login_time"] = "14.12.2025 15:30 Uhr"
case models.EmailTypeSuspiciousActivity:
vars["activity_type"] = "Mehrere fehlgeschlagene Logins"
vars["activity_time"] = "14.12.2025 15:30 Uhr"
vars["ip_address"] = "192.168.1.1"
case models.EmailTypeAccountLocked:
vars["locked_at"] = "14.12.2025 15:30 Uhr"
vars["reason"] = "Zu viele fehlgeschlagene Login-Versuche"
vars["unlock_time"] = "14.12.2025 16:30 Uhr"
case models.EmailTypeAccountUnlocked:
vars["unlocked_at"] = "14.12.2025 16:30 Uhr"
case models.EmailTypeDeletionRequested:
vars["requested_at"] = "14.12.2025 15:30 Uhr"
vars["deletion_date"] = "14.01.2026"
vars["cancel_url"] = "https://breakpilot.app/cancel-deletion?token=del123"
vars["data_info"] = "Profildaten, Consent-Historie, Audit-Logs"
case models.EmailTypeDeletionConfirmed:
vars["deleted_at"] = "14.01.2026 00:00 Uhr"
vars["feedback_url"] = "https://breakpilot.app/feedback"
case models.EmailTypeDataExportReady:
vars["download_url"] = "https://breakpilot.app/download/export123"
vars["expires_in"] = "7 Tage"
vars["file_size"] = "2.5 MB"
case models.EmailTypeEmailChanged:
vars["old_email"] = "old@example.com"
vars["new_email"] = "new@example.com"
vars["changed_at"] = "14.12.2025 15:30 Uhr"
case models.EmailTypeEmailChangeVerify:
vars["new_email"] = "new@example.com"
vars["verification_url"] = "https://breakpilot.app/verify-email?token=ver123"
vars["expires_in"] = "24 Stunden"
case models.EmailTypeNewVersionPublished:
vars["document_name"] = "Datenschutzerklärung"
vars["document_type"] = "privacy"
vars["version"] = "2.0.0"
vars["consent_url"] = "https://breakpilot.app/consent"
vars["deadline"] = "31.12.2025"
case models.EmailTypeConsentReminder:
vars["document_name"] = "Nutzungsbedingungen"
vars["days_left"] = "7"
vars["consent_url"] = "https://breakpilot.app/consent"
vars["deadline"] = "21.12.2025"
case models.EmailTypeConsentDeadlineWarning:
vars["document_name"] = "Nutzungsbedingungen"
vars["hours_left"] = "24 Stunden"
vars["consent_url"] = "https://breakpilot.app/consent"
vars["consequences"] = "Ihr Konto wird temporär suspendiert."
case models.EmailTypeAccountSuspended:
vars["suspended_at"] = "14.12.2025 15:30 Uhr"
vars["reason"] = "Fehlende Zustimmung zu Pflichtdokumenten"
vars["documents"] = "- Nutzungsbedingungen v2.0\n- Datenschutzerklärung v3.0"
vars["consent_url"] = "https://breakpilot.app/consent"
}
return vars
}
// TestEmailTemplateService_HTMLEscape tests that HTML is properly escaped in text version
func TestEmailTemplateService_HTMLEscape(t *testing.T) {
tests := []struct {
name string
html string
expected string
}{
{
name: "simple paragraph",
html: "<p>Hello World</p>",
expected: "Hello World",
},
{
name: "link",
html: `<a href="https://example.com">Click here</a>`,
expected: "Click here",
},
{
name: "bold text",
html: "<strong>Important</strong>",
expected: "Important",
},
{
name: "nested tags",
html: "<div><p><strong>Nested</strong> text</p></div>",
expected: "Nested text",
},
{
name: "multiple tags",
html: "<h1>Title</h1><p>Paragraph</p>",
expected: "TitleParagraph",
},
{
name: "self-closing tag",
html: "Line1<br/>Line2",
expected: "Line1Line2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := stripHTMLTags(tt.html)
if result != tt.expected {
t.Errorf("stripHTMLTags() = %s, want %s", result, tt.expected)
}
})
}
}
// stripHTMLTags removes HTML tags from a string
func stripHTMLTags(html string) string {
result := ""
inTag := false
for _, r := range html {
if r == '<' {
inTag = true
continue
}
if r == '>' {
inTag = false
continue
}
if !inTag {
result += string(r)
}
}
return result
}
// TestEmailTemplateService_AllTemplatesHaveVariables tests that all templates define their required variables
func TestEmailTemplateService_AllTemplatesHaveVariables(t *testing.T) {
service := &EmailTemplateService{}
templateTypes := service.GetAllTemplateTypes()
for _, tt := range templateTypes {
t.Run(tt.TemplateType, func(t *testing.T) {
// Get default template content
subject, bodyHTML, bodyText := service.GetDefaultTemplateContent(tt.TemplateType, "de")
// Check that variables defined in template type are present in the content
for _, varName := range tt.Variables {
placeholder := "{{" + varName + "}}"
foundInSubject := strings.Contains(subject, placeholder)
foundInHTML := strings.Contains(bodyHTML, placeholder)
foundInText := strings.Contains(bodyText, placeholder)
// Variable should be present in at least one of subject, HTML or text
if !foundInSubject && !foundInHTML && !foundInText {
// Note: This is a warning, not an error, as some variables might be optional
t.Logf("Warning: Variable %s defined for %s but not found in template content", varName, tt.TemplateType)
}
}
// Check that all variables in content are defined
re := regexp.MustCompile(`\{\{(\w+)\}\}`)
allMatches := re.FindAllStringSubmatch(subject+bodyHTML+bodyText, -1)
definedVars := make(map[string]bool)
for _, v := range tt.Variables {
definedVars[v] = true
}
for _, match := range allMatches {
if len(match) > 1 {
varName := match[1]
if !definedVars[varName] {
t.Logf("Warning: Variable {{%s}} found in template but not defined in variables list for %s", varName, tt.TemplateType)
}
}
}
})
}
}
// TestEmailTemplateService_TemplateVariableDescriptions tests that all variables have descriptions
func TestEmailTemplateService_TemplateVariableDescriptions(t *testing.T) {
service := &EmailTemplateService{}
templateTypes := service.GetAllTemplateTypes()
for _, tt := range templateTypes {
t.Run(tt.TemplateType, func(t *testing.T) {
for _, varName := range tt.Variables {
if desc, ok := tt.Descriptions[varName]; !ok || desc == "" {
t.Errorf("Variable %s in %s has no description", varName, tt.TemplateType)
}
}
})
}
}
// TestEmailTemplateService_GermanTemplatesAreComplete tests that all German templates are fully translated
func TestEmailTemplateService_GermanTemplatesAreComplete(t *testing.T) {
service := &EmailTemplateService{}
emailTypes := []string{
models.EmailTypeWelcome,
models.EmailTypeEmailVerification,
models.EmailTypePasswordReset,
models.EmailTypePasswordChanged,
models.EmailType2FAEnabled,
models.EmailType2FADisabled,
models.EmailTypeNewDeviceLogin,
models.EmailTypeSuspiciousActivity,
models.EmailTypeAccountLocked,
models.EmailTypeAccountUnlocked,
models.EmailTypeDeletionRequested,
models.EmailTypeDeletionConfirmed,
models.EmailTypeDataExportReady,
models.EmailTypeEmailChanged,
models.EmailTypeNewVersionPublished,
models.EmailTypeConsentReminder,
models.EmailTypeConsentDeadlineWarning,
models.EmailTypeAccountSuspended,
}
germanKeywords := []string{"Hallo", "freundlichen", "Grüßen", "BreakPilot", "Ihr"}
for _, emailType := range emailTypes {
t.Run(emailType, func(t *testing.T) {
subject, bodyHTML, bodyText := service.GetDefaultTemplateContent(emailType, "de")
// Check that German text is present
foundGerman := false
for _, keyword := range germanKeywords {
if strings.Contains(bodyHTML, keyword) || strings.Contains(bodyText, keyword) {
foundGerman = true
break
}
}
if !foundGerman {
t.Errorf("Template %s does not appear to be in German", emailType)
}
// Check that subject is not just the fallback
if subject == "No template" {
t.Errorf("Template %s has fallback subject instead of German subject", emailType)
}
})
}
}
// TestEmailTemplateService_HTMLStructure tests that HTML templates have valid structure
func TestEmailTemplateService_HTMLStructure(t *testing.T) {
service := &EmailTemplateService{}
emailTypes := []string{
models.EmailTypeWelcome,
models.EmailTypeEmailVerification,
models.EmailTypePasswordReset,
}
for _, emailType := range emailTypes {
t.Run(emailType, func(t *testing.T) {
_, bodyHTML, _ := service.GetDefaultTemplateContent(emailType, "de")
// Check for basic HTML structure
if !strings.Contains(bodyHTML, "<!DOCTYPE html>") {
t.Errorf("Template %s missing DOCTYPE", emailType)
}
if !strings.Contains(bodyHTML, "<html>") {
t.Errorf("Template %s missing <html> tag", emailType)
}
if !strings.Contains(bodyHTML, "</html>") {
t.Errorf("Template %s missing closing </html> tag", emailType)
}
if !strings.Contains(bodyHTML, "<body") {
t.Errorf("Template %s missing <body> tag", emailType)
}
if !strings.Contains(bodyHTML, "</body>") {
t.Errorf("Template %s missing closing </body> tag", emailType)
}
})
}
}
// BenchmarkReplaceVariables benchmarks variable replacement
func BenchmarkReplaceVariables(b *testing.B) {
template := "Hallo {{user_name}}, Ihr Link: {{reset_url}}, gültig bis {{expires_in}}"
variables := map[string]string{
"user_name": "Max Mustermann",
"reset_url": "https://example.com/reset?token=abc123",
"expires_in": "24 Stunden",
}
for i := 0; i < b.N; i++ {
replaceVariables(template, variables)
}
}
// BenchmarkStripHTMLTags benchmarks HTML tag stripping
func BenchmarkStripHTMLTags(b *testing.B) {
html := "<html><body><h1>Title</h1><p>This is a <strong>test</strong> paragraph with <a href='#'>links</a>.</p></body></html>"
for i := 0; i < b.N; i++ {
stripHTMLTags(html)
}
}