//go:build embed
package web
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
gin.SetMode(gin.TestMode)
}
func TestInjectSiteTitle(t *testing.T) {
t.Run("replaces_title_with_site_name", func(t *testing.T) {
html := []byte(`
Sub2API - AI API Gateway`)
settingsJSON := []byte(`{"site_name":"MyCustomSite"}`)
result := injectSiteTitle(html, settingsJSON)
assert.Contains(t, string(result), "MyCustomSite - AI API Gateway")
assert.NotContains(t, string(result), "Sub2API")
})
t.Run("returns_unchanged_when_site_name_empty", func(t *testing.T) {
html := []byte(`Sub2API - AI API Gateway`)
settingsJSON := []byte(`{"site_name":""}`)
result := injectSiteTitle(html, settingsJSON)
assert.Equal(t, string(html), string(result))
})
t.Run("returns_unchanged_when_site_name_missing", func(t *testing.T) {
html := []byte(`Sub2API - AI API Gateway`)
settingsJSON := []byte(`{"other_field":"value"}`)
result := injectSiteTitle(html, settingsJSON)
assert.Equal(t, string(html), string(result))
})
t.Run("returns_unchanged_when_invalid_json", func(t *testing.T) {
html := []byte(`Sub2API - AI API Gateway`)
settingsJSON := []byte(`{invalid json}`)
result := injectSiteTitle(html, settingsJSON)
assert.Equal(t, string(html), string(result))
})
t.Run("returns_unchanged_when_no_title_tag", func(t *testing.T) {
html := []byte(``)
settingsJSON := []byte(`{"site_name":"MyCustomSite"}`)
result := injectSiteTitle(html, settingsJSON)
assert.Equal(t, string(html), string(result))
})
t.Run("returns_unchanged_when_title_has_attributes", func(t *testing.T) {
// The function looks for "" literally, so attributes are not supported
// This is acceptable since index.html uses plain without attributes
html := []byte(`Sub2API`)
settingsJSON := []byte(`{"site_name":"NewSite"}`)
result := injectSiteTitle(html, settingsJSON)
// Should return unchanged since with attributes is not matched
assert.Equal(t, string(html), string(result))
})
t.Run("preserves_rest_of_html", func(t *testing.T) {
html := []byte(`Sub2API`)
settingsJSON := []byte(`{"site_name":"TestSite"}`)
result := injectSiteTitle(html, settingsJSON)
assert.Contains(t, string(result), ``)
assert.Contains(t, string(result), ``)
assert.Contains(t, string(result), ``)
assert.Contains(t, string(result), "TestSite - AI API Gateway")
})
}
func TestReplaceNoncePlaceholder(t *testing.T) {
t.Run("replaces_single_placeholder", func(t *testing.T) {
html := []byte(``)
nonce := "abc123xyz"
result := replaceNoncePlaceholder(html, nonce)
expected := ``
assert.Equal(t, expected, string(result))
})
t.Run("replaces_multiple_placeholders", func(t *testing.T) {
html := []byte(``)
nonce := "nonce123"
result := replaceNoncePlaceholder(html, nonce)
assert.Equal(t, 2, strings.Count(string(result), `nonce="nonce123"`))
assert.NotContains(t, string(result), NonceHTMLPlaceholder)
})
t.Run("handles_empty_nonce", func(t *testing.T) {
html := []byte(``)
nonce := ""
result := replaceNoncePlaceholder(html, nonce)
assert.Equal(t, ``, string(result))
})
t.Run("no_placeholder_returns_unchanged", func(t *testing.T) {
html := []byte(``)
nonce := "abc123"
result := replaceNoncePlaceholder(html, nonce)
assert.Equal(t, string(html), string(result))
})
t.Run("handles_empty_html", func(t *testing.T) {
html := []byte(``)
nonce := "abc123"
result := replaceNoncePlaceholder(html, nonce)
assert.Empty(t, result)
})
}
func TestNonceHTMLPlaceholder(t *testing.T) {
t.Run("constant_value", func(t *testing.T) {
assert.Equal(t, "__CSP_NONCE_VALUE__", NonceHTMLPlaceholder)
})
}
// mockSettingsProvider implements PublicSettingsProvider for testing
type mockSettingsProvider struct {
settings any
err error
called int
}
func (m *mockSettingsProvider) GetPublicSettingsForInjection(ctx context.Context) (any, error) {
m.called++
return m.settings, m.err
}
func TestFrontendServer_InjectSettings(t *testing.T) {
t.Run("injects_settings_with_nonce_placeholder", func(t *testing.T) {
provider := &mockSettingsProvider{
settings: map[string]string{"key": "value"},
}
server, err := NewFrontendServer(provider)
require.NoError(t, err)
settingsJSON := []byte(`{"test":"data"}`)
result := server.injectSettings(settingsJSON)
// Should contain the script with nonce placeholder
assert.Contains(t, string(result), ``)
})
t.Run("injects_before_head_close", func(t *testing.T) {
provider := &mockSettingsProvider{
settings: map[string]string{"key": "value"},
}
server, err := NewFrontendServer(provider)
require.NoError(t, err)
settingsJSON := []byte(`{}`)
result := server.injectSettings(settingsJSON)
// Script should be injected before
headCloseIndex := bytes.Index(result, []byte(""))
scriptIndex := bytes.Index(result, []byte(`