| |
|
|
| 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(`<html><head><title>Sub2API - AI API Gateway</title></head><body></body></html>`) |
| settingsJSON := []byte(`{"site_name":"MyCustomSite"}`) |
|
|
| result := injectSiteTitle(html, settingsJSON) |
|
|
| assert.Contains(t, string(result), "<title>MyCustomSite - AI API Gateway</title>") |
| assert.NotContains(t, string(result), "Sub2API") |
| }) |
|
|
| t.Run("returns_unchanged_when_site_name_empty", func(t *testing.T) { |
| html := []byte(`<html><head><title>Sub2API - AI API Gateway</title></head><body></body></html>`) |
| 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(`<html><head><title>Sub2API - AI API Gateway</title></head><body></body></html>`) |
| 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(`<html><head><title>Sub2API - AI API Gateway</title></head><body></body></html>`) |
| 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(`<html><head></head><body></body></html>`) |
| 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) { |
| |
| |
| html := []byte(`<html><head><title lang="en">Sub2API</title></head><body></body></html>`) |
| settingsJSON := []byte(`{"site_name":"NewSite"}`) |
|
|
| result := injectSiteTitle(html, settingsJSON) |
|
|
| |
| assert.Equal(t, string(html), string(result)) |
| }) |
|
|
| t.Run("preserves_rest_of_html", func(t *testing.T) { |
| html := []byte(`<html><head><meta charset="UTF-8"><title>Sub2API</title><script src="app.js"></script></head><body><div id="app"></div></body></html>`) |
| settingsJSON := []byte(`{"site_name":"TestSite"}`) |
|
|
| result := injectSiteTitle(html, settingsJSON) |
|
|
| assert.Contains(t, string(result), `<meta charset="UTF-8">`) |
| assert.Contains(t, string(result), `<script src="app.js"></script>`) |
| assert.Contains(t, string(result), `<div id="app"></div>`) |
| assert.Contains(t, string(result), "<title>TestSite - AI API Gateway</title>") |
| }) |
| } |
|
|
| func TestReplaceNoncePlaceholder(t *testing.T) { |
| t.Run("replaces_single_placeholder", func(t *testing.T) { |
| html := []byte(`<script nonce="__CSP_NONCE_VALUE__">console.log('test');</script>`) |
| nonce := "abc123xyz" |
|
|
| result := replaceNoncePlaceholder(html, nonce) |
|
|
| expected := `<script nonce="abc123xyz">console.log('test');</script>` |
| assert.Equal(t, expected, string(result)) |
| }) |
|
|
| t.Run("replaces_multiple_placeholders", func(t *testing.T) { |
| html := []byte(`<script nonce="__CSP_NONCE_VALUE__">a</script><script nonce="__CSP_NONCE_VALUE__">b</script>`) |
| 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(`<script nonce="__CSP_NONCE_VALUE__">test</script>`) |
| nonce := "" |
|
|
| result := replaceNoncePlaceholder(html, nonce) |
|
|
| assert.Equal(t, `<script nonce="">test</script>`, string(result)) |
| }) |
|
|
| t.Run("no_placeholder_returns_unchanged", func(t *testing.T) { |
| html := []byte(`<script>console.log('test');</script>`) |
| 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) |
| }) |
| } |
|
|
| |
| 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) |
|
|
| |
| assert.Contains(t, string(result), `<script nonce="__CSP_NONCE_VALUE__">`) |
| assert.Contains(t, string(result), `window.__APP_CONFIG__={"test":"data"};`) |
| assert.Contains(t, string(result), `</script></head>`) |
| }) |
|
|
| 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) |
|
|
| |
| headCloseIndex := bytes.Index(result, []byte("</head>")) |
| scriptIndex := bytes.Index(result, []byte(`<script nonce="`)) |
|
|
| assert.True(t, scriptIndex < headCloseIndex, "script should be before </head>") |
| }) |
|
|
| t.Run("handles_complex_settings", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]any{ |
| "nested": map[string]any{ |
| "array": []int{1, 2, 3}, |
| }, |
| }, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| settingsJSON := []byte(`{"nested":{"array":[1,2,3]},"special":"<>&"}`) |
| result := server.injectSettings(settingsJSON) |
|
|
| assert.Contains(t, string(result), `window.__APP_CONFIG__={"nested":{"array":[1,2,3]},"special":"<>&"};`) |
| }) |
| } |
|
|
| func TestFrontendServer_ServeIndexHTML(t *testing.T) { |
| t.Run("serves_html_with_nonce", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| |
| w := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(w) |
| c.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
|
|
| |
| testNonce := "test-nonce-12345" |
| c.Set(middleware.CSPNonceKey, testNonce) |
|
|
| server.serveIndexHTML(c) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "text/html") |
|
|
| body := w.Body.String() |
| |
| assert.NotContains(t, body, NonceHTMLPlaceholder) |
| assert.Contains(t, body, `nonce="`+testNonce+`"`) |
| }) |
|
|
| t.Run("caches_html_content", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| |
| w1 := httptest.NewRecorder() |
| c1, _ := gin.CreateTestContext(w1) |
| c1.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c1.Set(middleware.CSPNonceKey, "nonce1") |
|
|
| server.serveIndexHTML(c1) |
| assert.Equal(t, 1, provider.called) |
|
|
| |
| w2 := httptest.NewRecorder() |
| c2, _ := gin.CreateTestContext(w2) |
| c2.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c2.Set(middleware.CSPNonceKey, "nonce2") |
|
|
| server.serveIndexHTML(c2) |
| |
| assert.Equal(t, 1, provider.called) |
|
|
| |
| assert.Contains(t, w2.Body.String(), `nonce="nonce2"`) |
| }) |
|
|
| t.Run("sets_etag_header", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| w := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(w) |
| c.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c.Set(middleware.CSPNonceKey, "nonce123") |
|
|
| server.serveIndexHTML(c) |
|
|
| etag := w.Header().Get("ETag") |
| assert.NotEmpty(t, etag) |
| assert.True(t, strings.HasPrefix(etag, `"`)) |
| assert.True(t, strings.HasSuffix(etag, `"`)) |
| }) |
|
|
| t.Run("returns_304_for_matching_etag", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| |
| router := gin.New() |
| router.Use(func(c *gin.Context) { |
| c.Set(middleware.CSPNonceKey, "test-nonce") |
| c.Next() |
| }) |
| router.Use(server.Middleware()) |
|
|
| |
| w1 := httptest.NewRecorder() |
| req1 := httptest.NewRequest(http.MethodGet, "/", nil) |
| router.ServeHTTP(w1, req1) |
| etag := w1.Header().Get("ETag") |
| require.NotEmpty(t, etag) |
|
|
| |
| w2 := httptest.NewRecorder() |
| req2 := httptest.NewRequest(http.MethodGet, "/", nil) |
| req2.Header.Set("If-None-Match", etag) |
| router.ServeHTTP(w2, req2) |
|
|
| assert.Equal(t, http.StatusNotModified, w2.Code) |
| assert.Empty(t, w2.Body.String()) |
| }) |
|
|
| t.Run("sets_cache_control_header", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| w := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(w) |
| c.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c.Set(middleware.CSPNonceKey, "nonce123") |
|
|
| server.serveIndexHTML(c) |
|
|
| assert.Equal(t, "no-cache", w.Header().Get("Cache-Control")) |
| }) |
|
|
| t.Run("fallback_on_settings_error", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| err: context.DeadlineExceeded, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| |
| server.InvalidateCache() |
|
|
| w := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(w) |
| c.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c.Set(middleware.CSPNonceKey, "nonce123") |
|
|
| server.serveIndexHTML(c) |
|
|
| |
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "text/html") |
| }) |
| } |
|
|
| func TestFrontendServer_InvalidateCache(t *testing.T) { |
| t.Run("invalidates_cache", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| |
| w1 := httptest.NewRecorder() |
| c1, _ := gin.CreateTestContext(w1) |
| c1.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c1.Set(middleware.CSPNonceKey, "nonce1") |
|
|
| server.serveIndexHTML(c1) |
| assert.Equal(t, 1, provider.called) |
|
|
| |
| server.InvalidateCache() |
|
|
| |
| provider.settings = map[string]string{"test": "new_value"} |
|
|
| |
| w2 := httptest.NewRecorder() |
| c2, _ := gin.CreateTestContext(w2) |
| c2.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c2.Set(middleware.CSPNonceKey, "nonce2") |
|
|
| server.serveIndexHTML(c2) |
| assert.Equal(t, 2, provider.called) |
| }) |
|
|
| t.Run("handles_nil_server", func(t *testing.T) { |
| var server *FrontendServer |
| |
| assert.NotPanics(t, func() { |
| server.InvalidateCache() |
| }) |
| }) |
|
|
| t.Run("handles_nil_cache", func(t *testing.T) { |
| server := &FrontendServer{} |
| |
| assert.NotPanics(t, func() { |
| server.InvalidateCache() |
| }) |
| }) |
| } |
|
|
| func TestFrontendServer_Middleware(t *testing.T) { |
| t.Run("skips_api_routes", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| apiPaths := []string{ |
| "/api/v1/users", |
| "/v1/models", |
| "/v1beta/chat", |
| "/sora/v1/models", |
| "/antigravity/test", |
| "/setup/init", |
| "/health", |
| "/responses", |
| "/responses/compact", |
| } |
|
|
| for _, path := range apiPaths { |
| t.Run(path, func(t *testing.T) { |
| router := gin.New() |
| router.Use(server.Middleware()) |
| nextCalled := false |
| router.GET(path, func(c *gin.Context) { |
| nextCalled = true |
| c.String(http.StatusOK, "ok") |
| }) |
|
|
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, path, nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.True(t, nextCalled, "next handler should be called for API route") |
| }) |
| } |
| }) |
|
|
| t.Run("skips_responses_compact_post_routes", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| router := gin.New() |
| router.Use(server.Middleware()) |
| nextCalled := false |
| router.POST("/responses/compact", func(c *gin.Context) { |
| nextCalled = true |
| c.String(http.StatusOK, `{"ok":true}`) |
| }) |
|
|
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodPost, "/responses/compact", strings.NewReader(`{"model":"gpt-5"}`)) |
| req.Header.Set("Content-Type", "application/json") |
| router.ServeHTTP(w, req) |
|
|
| assert.True(t, nextCalled, "next handler should be called for compact API route") |
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.JSONEq(t, `{"ok":true}`, w.Body.String()) |
| }) |
|
|
| t.Run("serves_index_for_spa_routes", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| router := gin.New() |
| router.Use(func(c *gin.Context) { |
| c.Set(middleware.CSPNonceKey, "test-nonce") |
| c.Next() |
| }) |
| router.Use(server.Middleware()) |
|
|
| spaPaths := []string{ |
| "/", |
| "/dashboard", |
| "/users/123", |
| "/settings/profile", |
| } |
|
|
| for _, path := range spaPaths { |
| t.Run(path, func(t *testing.T) { |
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, path, nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "text/html") |
| }) |
| } |
| }) |
|
|
| t.Run("serves_static_files", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| router := gin.New() |
| router.Use(server.Middleware()) |
|
|
| |
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, "/logo.png", nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "image/png") |
| }) |
| } |
|
|
| func TestNewFrontendServer(t *testing.T) { |
| t.Run("creates_server_successfully", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
|
|
| require.NoError(t, err) |
| assert.NotNil(t, server) |
| assert.NotNil(t, server.distFS) |
| assert.NotNil(t, server.fileServer) |
| assert.NotNil(t, server.baseHTML) |
| assert.NotNil(t, server.cache) |
| assert.Equal(t, provider, server.settings) |
| }) |
|
|
| t.Run("reads_base_html", func(t *testing.T) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, err := NewFrontendServer(provider) |
| require.NoError(t, err) |
|
|
| assert.NotEmpty(t, server.baseHTML) |
| assert.Contains(t, string(server.baseHTML), "<!doctype html>") |
| }) |
| } |
|
|
| func TestHasEmbeddedFrontend(t *testing.T) { |
| t.Run("returns_true_when_frontend_embedded", func(t *testing.T) { |
| result := HasEmbeddedFrontend() |
| assert.True(t, result) |
| }) |
| } |
|
|
| |
| func TestServeEmbeddedFrontend(t *testing.T) { |
| t.Run("serves_static_files", func(t *testing.T) { |
| middleware := ServeEmbeddedFrontend() |
|
|
| router := gin.New() |
| router.Use(middleware) |
|
|
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, "/logo.png", nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "image/png") |
| }) |
|
|
| t.Run("serves_index_html_for_root", func(t *testing.T) { |
| middleware := ServeEmbeddedFrontend() |
|
|
| router := gin.New() |
| router.Use(middleware) |
|
|
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, "/", nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "text/html") |
| assert.Contains(t, w.Body.String(), "<!doctype html>") |
| }) |
|
|
| t.Run("serves_index_html_for_spa_routes", func(t *testing.T) { |
| middleware := ServeEmbeddedFrontend() |
|
|
| router := gin.New() |
| router.Use(middleware) |
|
|
| spaPaths := []string{"/dashboard", "/users/123", "/settings"} |
|
|
| for _, path := range spaPaths { |
| t.Run(path, func(t *testing.T) { |
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, path, nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.Equal(t, http.StatusOK, w.Code) |
| assert.Contains(t, w.Header().Get("Content-Type"), "text/html") |
| }) |
| } |
| }) |
|
|
| t.Run("skips_api_routes", func(t *testing.T) { |
| middleware := ServeEmbeddedFrontend() |
|
|
| apiPaths := []string{ |
| "/api/users", |
| "/v1/models", |
| "/v1beta/chat", |
| "/sora/v1/models", |
| "/antigravity/test", |
| "/setup/init", |
| "/health", |
| "/responses", |
| "/responses/compact", |
| } |
|
|
| for _, path := range apiPaths { |
| t.Run(path, func(t *testing.T) { |
| nextCalled := false |
| router := gin.New() |
| router.Use(middleware) |
| router.GET(path, func(c *gin.Context) { |
| nextCalled = true |
| c.String(http.StatusOK, "ok") |
| }) |
|
|
| w := httptest.NewRecorder() |
| req := httptest.NewRequest(http.MethodGet, path, nil) |
| router.ServeHTTP(w, req) |
|
|
| assert.True(t, nextCalled, "next handler should be called for API route") |
| }) |
| } |
| }) |
| } |
|
|
| |
| func TestHTMLCache(t *testing.T) { |
| t.Run("new_cache_returns_nil", func(t *testing.T) { |
| cache := NewHTMLCache() |
| assert.Nil(t, cache.Get()) |
| }) |
|
|
| t.Run("set_and_get", func(t *testing.T) { |
| cache := NewHTMLCache() |
| cache.SetBaseHTML([]byte("<html></html>")) |
|
|
| html := []byte("<html><body>test</body></html>") |
| settings := []byte(`{"key":"value"}`) |
| cache.Set(html, settings) |
|
|
| result := cache.Get() |
| require.NotNil(t, result) |
| assert.Equal(t, html, result.Content) |
| assert.NotEmpty(t, result.ETag) |
| }) |
|
|
| t.Run("invalidate_clears_cache", func(t *testing.T) { |
| cache := NewHTMLCache() |
| cache.SetBaseHTML([]byte("<html></html>")) |
|
|
| html := []byte("<html><body>test</body></html>") |
| settings := []byte(`{"key":"value"}`) |
| cache.Set(html, settings) |
|
|
| require.NotNil(t, cache.Get()) |
|
|
| cache.Invalidate() |
|
|
| assert.Nil(t, cache.Get()) |
| }) |
|
|
| t.Run("etag_changes_with_settings", func(t *testing.T) { |
| cache := NewHTMLCache() |
| cache.SetBaseHTML([]byte("<html></html>")) |
|
|
| html := []byte("<html><body>test</body></html>") |
|
|
| cache.Set(html, []byte(`{"v":1}`)) |
| etag1 := cache.Get().ETag |
|
|
| cache.Invalidate() |
| cache.Set(html, []byte(`{"v":2}`)) |
| etag2 := cache.Get().ETag |
|
|
| assert.NotEqual(t, etag1, etag2) |
| }) |
|
|
| t.Run("etag_format", func(t *testing.T) { |
| cache := NewHTMLCache() |
| cache.SetBaseHTML([]byte("<html></html>")) |
|
|
| cache.Set([]byte("<html></html>"), []byte(`{}`)) |
| result := cache.Get() |
|
|
| |
| assert.True(t, strings.HasPrefix(result.ETag, `"`)) |
| assert.True(t, strings.HasSuffix(result.ETag, `"`)) |
| |
| assert.Contains(t, result.ETag[1:len(result.ETag)-1], "-") |
| }) |
| } |
|
|
| |
| func BenchmarkReplaceNoncePlaceholder(b *testing.B) { |
| html := []byte(`<!DOCTYPE html><html><head><script nonce="__CSP_NONCE_VALUE__">window.__APP_CONFIG__={"test":"data"};</script></head><body></body></html>`) |
| nonce := "abcdefghijklmnop123456==" |
|
|
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| replaceNoncePlaceholder(html, nonce) |
| } |
| } |
|
|
| func BenchmarkFrontendServerServeIndexHTML(b *testing.B) { |
| provider := &mockSettingsProvider{ |
| settings: map[string]string{"test": "value"}, |
| } |
|
|
| server, _ := NewFrontendServer(provider) |
|
|
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| w := httptest.NewRecorder() |
| c, _ := gin.CreateTestContext(w) |
| c.Request = httptest.NewRequest(http.MethodGet, "/", nil) |
| c.Set(middleware.CSPNonceKey, "test-nonce") |
|
|
| server.serveIndexHTML(c) |
| } |
| } |
|
|