package handlers import ( "context" "encoding/json" "log/slog" "net/http" "os" "testing" "time" archivalDomain "git.nakama.town/fmartingr/hako/internal/archival/domain" authDomain "git.nakama.town/fmartingr/hako/internal/auth/domain" "git.nakama.town/fmartingr/hako/internal/model" "git.nakama.town/fmartingr/hako/internal/server/middleware" "git.nakama.town/fmartingr/hako/internal/testutil" "github.com/google/uuid" "github.com/stretchr/testify/require" ) func TestHandleCreateLink_Success(t *testing.T) { ctx := context.Background() deps := testutil.GetTestConfigurationAndDependencies(t, ctx) // Initialize domains deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies)) deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies)) deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies)) deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies)) // Create a test user and get token _, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123") require.NoError(t, err) // Create logger for tests logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) // Create link handler linkHandler := NewLinkHandler(deps.Dependencies, logger) // Create handlers with auth middleware globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore) requireAuthMiddleware := middleware.RequireAuthMiddleware() handler := globalAuthMiddleware(requireAuthMiddleware(http.HandlerFunc(linkHandler.HandleCreateLink))) // Test request requestBody := `{"url": "https://example.com"}` w := testutil.PerformRequestWithHandler(handler, http.MethodPost, "/api/v1/links", testutil.WithAuthToken(token), testutil.WithBody(requestBody), ) // Assert response resp := testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusCreated) // Check response structure var linkResp model.LinkListItem err = json.Unmarshal(resp.GetBody(), &linkResp) require.NoError(t, err) require.NotEmpty(t, linkResp.ID) require.Equal(t, "https://example.com", linkResp.URL) } func TestHandleCreateLink_MissingURL(t *testing.T) { ctx := context.Background() deps := testutil.GetTestConfigurationAndDependencies(t, ctx) // Initialize domains deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies)) deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies)) deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies)) deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies)) // Create a test user and get token _, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123") require.NoError(t, err) // Create logger for tests logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) // Create link handler linkHandler := NewLinkHandler(deps.Dependencies, logger) // Create handlers with auth middleware globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore) requireAuthMiddleware := middleware.RequireAuthMiddleware() handler := globalAuthMiddleware(requireAuthMiddleware(http.HandlerFunc(linkHandler.HandleCreateLink))) // Test request with empty URL requestBody := `{"url": ""}` w := testutil.PerformRequestWithHandler(handler, http.MethodPost, "/api/v1/links", testutil.WithAuthToken(token), testutil.WithBody(requestBody), ) // Assert response resp := testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusBadRequest) } func TestHandleCreateLink_Unauthorized(t *testing.T) { ctx := context.Background() deps := testutil.GetTestConfigurationAndDependencies(t, ctx) // Create logger for tests logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) // Create link handler linkHandler := NewLinkHandler(deps.Dependencies, logger) // Create handlers with auth middleware globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore) requireAuthMiddleware := middleware.RequireAuthMiddleware() handler := globalAuthMiddleware(requireAuthMiddleware(http.HandlerFunc(linkHandler.HandleCreateLink))) // Test request without token requestBody := `{"url": "https://example.com"}` w := testutil.PerformRequestWithHandler(handler, http.MethodPost, "/api/v1/links", testutil.WithBody(requestBody), ) // Assert response resp := testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusUnauthorized) } func TestHandleListLinks_Success(t *testing.T) { ctx := context.Background() deps := testutil.GetTestConfigurationAndDependencies(t, ctx) // Initialize domains deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies)) deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies)) deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies)) deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies)) // Create a test user and get token _, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123") require.NoError(t, err) // Create logger for tests logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) // Create link handler linkHandler := NewLinkHandler(deps.Dependencies, logger) // Create handlers with auth middleware globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore) requireAuthMiddleware := middleware.RequireAuthMiddleware() handler := globalAuthMiddleware(requireAuthMiddleware(http.HandlerFunc(linkHandler.HandleListLinks))) // Test request w := testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links", testutil.WithAuthToken(token), ) // Assert response resp := testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) // Check response structure var listResp ListLinksResponse err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) require.NotNil(t, listResp.Items) require.GreaterOrEqual(t, listResp.Total, 0) } func TestHandleListLinks_WithSearch(t *testing.T) { ctx := context.Background() deps := testutil.GetTestConfigurationAndDependencies(t, ctx) // Initialize domains deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies)) deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies)) deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies)) deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies)) // Create a test user and get token user, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123") require.NoError(t, err) // Create logger for tests logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) // Create link handler linkHandler := NewLinkHandler(deps.Dependencies, logger) // Create handlers with auth middleware globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore) requireAuthMiddleware := middleware.RequireAuthMiddleware() handler := globalAuthMiddleware(requireAuthMiddleware(http.HandlerFunc(linkHandler.HandleListLinks))) // Create a link with a searchable URL linkDomain := deps.Dependencies.Domains().Links() link, err := linkDomain.CreateLink(ctx, "https://example.com/searchable", user.ID.String()) require.NoError(t, err) // Create an archive with a searchable title now := time.Now() archive := &model.Archive{ ID: uuid.New().String(), LinkID: link.ID, UserID: user.ID.String(), Status: model.ArchiveStatusCompleted, Title: "Searchable Title Content", CreatedAt: now, } err = deps.Dependencies.ArchiveStore.Create(ctx, archive) require.NoError(t, err) // Create an archive file with searchable content archiveFile := &model.ArchiveFile{ ID: uuid.New().String(), ArchiveID: archive.ID, ArchiverKey: "test", Filename: "test.txt", MimeType: "text/plain", Content: "This is searchable content text", CreatedAt: now, } err = deps.Dependencies.ArchiveFileStore.Create(ctx, archiveFile) require.NoError(t, err) // Test search by URL w := testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links?q=searchable", testutil.WithAuthToken(token), ) resp := testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) var listResp ListLinksResponse err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) require.GreaterOrEqual(t, len(listResp.Items), 1, "Should find link by URL search") require.Equal(t, link.ID, listResp.Items[0].ID) // Test search by title w = testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links?q=Title", testutil.WithAuthToken(token), ) resp = testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) require.GreaterOrEqual(t, len(listResp.Items), 1, "Should find link by title search") // Test that search queries with less than 3 characters are ignored w = testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links?q=ab", testutil.WithAuthToken(token), ) resp = testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) // Should return all links (no search filter applied) require.GreaterOrEqual(t, len(listResp.Items), 1, "Should return all links when query is too short") // Test search by content w = testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links?q=content", testutil.WithAuthToken(token), ) resp = testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) require.GreaterOrEqual(t, len(listResp.Items), 1, "Should find link by content search") // Test search with no results w = testutil.PerformRequestWithHandler(handler, http.MethodGet, "/api/v1/links?q=nonexistentterm12345", testutil.WithAuthToken(token), ) resp = testutil.NewTestResponse(w) resp.AssertStatus(t, http.StatusOK) err = json.Unmarshal(resp.GetBody(), &listResp) require.NoError(t, err) require.Equal(t, 0, len(listResp.Items), "Should return no results for nonexistent term") }