package handlers_test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" "sneak.berlin/go/webhooker/internal/config" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/delivery" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/handlers" "sneak.berlin/go/webhooker/internal/healthcheck" "sneak.berlin/go/webhooker/internal/logger" "sneak.berlin/go/webhooker/internal/session" ) type noopNotifier struct{} func (n *noopNotifier) Notify([]delivery.Task) {} func newTestApp( t *testing.T, targets ...any, ) *fxtest.App { t.Helper() return fxtest.New( t, fx.Provide( globals.New, logger.New, func() *config.Config { return &config.Config{ DataDir: t.TempDir(), } }, database.New, database.NewWebhookDBManager, healthcheck.New, session.New, func() delivery.Notifier { return &noopNotifier{} }, handlers.New, ), fx.Populate(targets...), ) } func TestHandleIndex_Unauthenticated(t *testing.T) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) req := httptest.NewRequestWithContext( context.Background(), http.MethodGet, "/", nil) w := httptest.NewRecorder() handler := h.HandleIndex() handler.ServeHTTP(w, req) assert.Equal(t, http.StatusSeeOther, w.Code) assert.Equal( t, "/pages/login", w.Header().Get("Location"), ) } func TestHandleIndex_Authenticated(t *testing.T) { t.Parallel() var h *handlers.Handlers var sess *session.Session app := newTestApp(t, &h, &sess) app.RequireStart() t.Cleanup(app.RequireStop) req := httptest.NewRequestWithContext( context.Background(), http.MethodGet, "/", nil) w := httptest.NewRecorder() s, err := sess.Get(req) require.NoError(t, err) sess.SetUser(s, "test-user-id", "testuser") err = sess.Save(req, w, s) require.NoError(t, err) req2 := httptest.NewRequestWithContext( context.Background(), http.MethodGet, "/", nil) for _, cookie := range w.Result().Cookies() { req2.AddCookie(cookie) } w2 := httptest.NewRecorder() h.HandleIndex().ServeHTTP(w2, req2) assert.Equal(t, http.StatusSeeOther, w2.Code) assert.Equal( t, "/sources", w2.Header().Get("Location"), ) } func TestBuildSlackTargetConfig_AcceptsPublicURL(t *testing.T) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) req := httptest.NewRequestWithContext( context.Background(), http.MethodPost, "/", nil) w := httptest.NewRecorder() cfg, err := h.BuildSlackTargetConfigForTest( w, req, "http://93.184.216.34/services/T00/B00/xxx", ) require.NoError(t, err) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, cfg, "webhookUrl") } func TestBuildSlackTargetConfig_RejectsReservedURL(t *testing.T) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) req := httptest.NewRequestWithContext( context.Background(), http.MethodPost, "/", nil) w := httptest.NewRecorder() cfg, err := h.BuildSlackTargetConfigForTest( w, req, "http://169.254.169.254/latest/meta-data/", ) require.Error(t, err) assert.Empty(t, cfg) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestRenderTemplate(t *testing.T) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) req := httptest.NewRequestWithContext( context.Background(), http.MethodGet, "/", nil) w := httptest.NewRecorder() data := map[string]any{"Version": "1.0.0"} h.RenderTemplateForTest( w, req, "nonexistent.html", data, ) assert.Equal( t, http.StatusInternalServerError, w.Code, ) } func TestBuildDatabaseTargetConfig_Valid(t *testing.T) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) // Empty expiry: the keep-forever default, empty config. w := httptest.NewRecorder() cfg, err := h.BuildDatabaseTargetConfigForTest(w, "") require.NoError(t, err) assert.Empty(t, cfg) // Explicit never is stored as config. w = httptest.NewRecorder() cfg, err = h.BuildDatabaseTargetConfigForTest(w, "never") require.NoError(t, err) assert.JSONEq(t, `{"expiry":"never"}`, cfg) // A positive duration is stored as config. w = httptest.NewRecorder() cfg, err = h.BuildDatabaseTargetConfigForTest(w, "720h") require.NoError(t, err) assert.JSONEq(t, `{"expiry":"720h"}`, cfg) } func TestBuildDatabaseTargetConfig_RejectsBadExpiry( t *testing.T, ) { t.Parallel() var h *handlers.Handlers app := newTestApp(t, &h) app.RequireStart() t.Cleanup(app.RequireStop) for _, bad := range []string{"nonsense", "7d", "-5h"} { w := httptest.NewRecorder() cfg, err := h.BuildDatabaseTargetConfigForTest(w, bad) require.Error(t, err, "expiry %q", bad) assert.Empty(t, cfg) assert.Equal( t, http.StatusBadRequest, w.Code, "expiry %q should be rejected with 400", bad, ) } }