diff --git a/pkg/webtests/huma_backgrounds_misc_test.go b/pkg/webtests/huma_background_test.go
similarity index 55%
rename from pkg/webtests/huma_backgrounds_misc_test.go
rename to pkg/webtests/huma_background_test.go
index e276b2fdb..8efdc3c2b 100644
--- a/pkg/webtests/huma_backgrounds_misc_test.go
+++ b/pkg/webtests/huma_background_test.go
@@ -17,7 +17,6 @@
package webtests
import (
- "encoding/json"
"net/http"
"testing"
@@ -30,89 +29,6 @@ import (
"github.com/stretchr/testify/require"
)
-// TestHumaInfo covers the public instance-info endpoint. It needs no auth and
-// always reports the running version.
-func TestHumaInfo(t *testing.T) {
- e, err := setupTestEnv()
- require.NoError(t, err)
-
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/info", "", "", "")
- require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
-
- var body map[string]any
- require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
- assert.Contains(t, body, "version")
- assert.Contains(t, body, "auth")
- assert.Contains(t, body, "available_migrators")
-}
-
-// TestHumaWebhookEvents covers the available-webhook-events listing. The route
-// is only registered when webhooks are enabled (the test config default).
-func TestHumaWebhookEvents(t *testing.T) {
- e, err := setupTestEnv()
- require.NoError(t, err)
-
- t.Run("Returns the events", func(t *testing.T) {
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/webhooks/events", "", humaTokenFor(t, &testuser1), "")
- require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
-
- var events []string
- require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &events))
- assert.ElementsMatch(t, models.GetAvailableWebhookEvents(), events)
- })
- t.Run("Unauthenticated", func(t *testing.T) {
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/webhooks/events", "", "", "")
- assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
- })
-}
-
-// TestHumaUserSearch covers the global user search. Emails must never leak.
-func TestHumaUserSearch(t *testing.T) {
- e, err := setupTestEnv()
- require.NoError(t, err)
- token := humaTokenFor(t, &testuser1)
-
- t.Run("Search by username", func(t *testing.T) {
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/users?q=user2", "", token, "")
- require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
-
- usernames, emails := usersFromSearch(t, rec.Body.Bytes())
- assert.Contains(t, usernames, "user2")
- for _, em := range emails {
- assert.Empty(t, em, "user search must never return email addresses")
- }
- })
- t.Run("Unauthenticated", func(t *testing.T) {
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/users?q=user2", "", "", "")
- assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
- })
-}
-
-// TestHumaProjectUserSearch covers the per-project user search used for share
-// autocomplete. It requires read access to the project.
-func TestHumaProjectUserSearch(t *testing.T) {
- e, err := setupTestEnv()
- require.NoError(t, err)
- token := humaTokenFor(t, &testuser1)
-
- t.Run("Owned project", func(t *testing.T) {
- // testuser1 owns project 1.
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/1/users/search", "", token, "")
- require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
- assert.Contains(t, rec.Body.String(), `"items"`)
- })
- t.Run("Forbidden - no access", func(t *testing.T) {
- // project 2 is owned by user3; testuser1 has no access.
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/2/users/search", "", token, "")
- assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
- })
- t.Run("Nonexistent project", func(t *testing.T) {
- // CanRead surfaces ErrProjectDoesNotExist (404), not a bare forbidden.
- rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/99999/users/search", "", token, "")
- assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String())
- })
-}
-
// TestHumaProjectBackgroundDelete covers removing a project background. It
// mirrors the v1 background_test.go matrix: the owner clears the background
// (and keeps the title), a read-only user is refused.
@@ -194,19 +110,3 @@ func TestHumaUnsplashBackground(t *testing.T) {
assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
})
}
-
-func usersFromSearch(t *testing.T, body []byte) (usernames, emails []string) {
- t.Helper()
- var resp struct {
- Items []struct {
- Username string `json:"username"`
- Email string `json:"email"`
- } `json:"items"`
- }
- require.NoError(t, json.Unmarshal(body, &resp), "search body must be a paginated envelope: %s", string(body))
- for _, it := range resp.Items {
- usernames = append(usernames, it.Username)
- emails = append(emails, it.Email)
- }
- return usernames, emails
-}
diff --git a/pkg/webtests/huma_info_test.go b/pkg/webtests/huma_info_test.go
new file mode 100644
index 000000000..5ba7f859c
--- /dev/null
+++ b/pkg/webtests/huma_info_test.go
@@ -0,0 +1,42 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestHumaInfo covers the public instance-info endpoint. It needs no auth and
+// always reports the running version.
+func TestHumaInfo(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/info", "", "", "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+
+ var body map[string]any
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
+ assert.Contains(t, body, "version")
+ assert.Contains(t, body, "auth")
+ assert.Contains(t, body, "available_migrators")
+}
diff --git a/pkg/webtests/huma_user_search_test.go b/pkg/webtests/huma_user_search_test.go
new file mode 100644
index 000000000..821095ea6
--- /dev/null
+++ b/pkg/webtests/huma_user_search_test.go
@@ -0,0 +1,89 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestHumaUserSearch covers the global user search. Emails must never leak.
+func TestHumaUserSearch(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ token := humaTokenFor(t, &testuser1)
+
+ t.Run("Search by username", func(t *testing.T) {
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/users?q=user2", "", token, "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+
+ usernames, emails := usersFromSearch(t, rec.Body.Bytes())
+ assert.Contains(t, usernames, "user2")
+ for _, em := range emails {
+ assert.Empty(t, em, "user search must never return email addresses")
+ }
+ })
+ t.Run("Unauthenticated", func(t *testing.T) {
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/users?q=user2", "", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
+ })
+}
+
+// TestHumaProjectUserSearch covers the per-project user search used for share
+// autocomplete. It requires read access to the project.
+func TestHumaProjectUserSearch(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ token := humaTokenFor(t, &testuser1)
+
+ t.Run("Owned project", func(t *testing.T) {
+ // testuser1 owns project 1.
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/1/users/search", "", token, "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Contains(t, rec.Body.String(), `"items"`)
+ })
+ t.Run("Forbidden - no access", func(t *testing.T) {
+ // project 2 is owned by user3; testuser1 has no access.
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/2/users/search", "", token, "")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+ })
+ t.Run("Nonexistent project", func(t *testing.T) {
+ // CanRead surfaces ErrProjectDoesNotExist (404), not a bare forbidden.
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/projects/99999/users/search", "", token, "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String())
+ })
+}
+
+func usersFromSearch(t *testing.T, body []byte) (usernames, emails []string) {
+ t.Helper()
+ var resp struct {
+ Items []struct {
+ Username string `json:"username"`
+ Email string `json:"email"`
+ } `json:"items"`
+ }
+ require.NoError(t, json.Unmarshal(body, &resp), "search body must be a paginated envelope: %s", string(body))
+ for _, it := range resp.Items {
+ usernames = append(usernames, it.Username)
+ emails = append(emails, it.Email)
+ }
+ return usernames, emails
+}
diff --git a/pkg/webtests/huma_webhook_event_test.go b/pkg/webtests/huma_webhook_event_test.go
new file mode 100644
index 000000000..6db2dbc5d
--- /dev/null
+++ b/pkg/webtests/huma_webhook_event_test.go
@@ -0,0 +1,48 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "code.vikunja.io/api/pkg/models"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestHumaWebhookEvents covers the available-webhook-events listing. The route
+// is only registered when webhooks are enabled (the test config default).
+func TestHumaWebhookEvents(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ t.Run("Returns the events", func(t *testing.T) {
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/webhooks/events", "", humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+
+ var events []string
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &events))
+ assert.ElementsMatch(t, models.GetAvailableWebhookEvents(), events)
+ })
+ t.Run("Unauthenticated", func(t *testing.T) {
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/webhooks/events", "", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
+ })
+}