test(api/v2): isolate project tests per-handler, not via shared harness
The project test port had added db.LoadFixtures() into the shared webHandlerTestV2.serve(), reloading fixtures before every request. That wiped runtime-created rows between requests within a test, breaking the create-then-read-back contract every v2 resource relies on (e.g. TestHumaTeam/Create/Public read its freshly-created team back and got 403). Revert that shared-harness change and isolate the project/archived tests the way the team and label tests do: each subtest builds its own handler via handlerFor, so it runs against freshly loaded fixtures (setupTestEnv reloads once per handler), while a create-then-read-back sequence reuses one handler within the subtest.
This commit is contained in:
parent
bec991288b
commit
33b9aa6292
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/models"
|
"code.vikunja.io/api/pkg/models"
|
||||||
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -49,16 +50,17 @@ import (
|
||||||
// HTTP surface to exercise them through. They remain proven by the v1 webtest
|
// HTTP surface to exercise them through. They remain proven by the v1 webtest
|
||||||
// and by the model-level TestCheckIsArchived until those resources are ported.
|
// and by the model-level TestCheckIsArchived until those resources are ported.
|
||||||
func TestHumaArchived(t *testing.T) {
|
func TestHumaArchived(t *testing.T) {
|
||||||
testHandler := webHandlerTestV2{
|
// Each subtest gets a pristine handler: the shared serve() does not reload
|
||||||
user: &testuser1,
|
// fixtures per request, so the un-archive/archive mutations below must not
|
||||||
basePath: "/api/v2/projects",
|
// leak across subtests (mirrors huma_team_test.go's per-subtest isolation).
|
||||||
idParam: "project",
|
handlerFor := func(u *user.User) *webHandlerTestV2 {
|
||||||
t: t,
|
return &webHandlerTestV2{user: u, basePath: "/api/v2/projects", idParam: "project", t: t}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The project belongs to an archived parent project.
|
// The project belongs to an archived parent project.
|
||||||
t.Run("archived parent project", func(t *testing.T) {
|
t.Run("archived parent project", func(t *testing.T) {
|
||||||
t.Run("not editable", func(t *testing.T) {
|
t.Run("not editable", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
||||||
|
|
@ -67,6 +69,7 @@ func TestHumaArchived(t *testing.T) {
|
||||||
t.Run("not unarchivable", func(t *testing.T) {
|
t.Run("not unarchivable", func(t *testing.T) {
|
||||||
// The un-archive exception only applies to the self-archived
|
// The un-archive exception only applies to the self-archived
|
||||||
// project; here the archived ancestor (22) still blocks it.
|
// project; here the archived ancestor (22) still blocks it.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
||||||
|
|
@ -77,12 +80,14 @@ func TestHumaArchived(t *testing.T) {
|
||||||
// The project itself is archived.
|
// The project itself is archived.
|
||||||
t.Run("archived individually", func(t *testing.T) {
|
t.Run("archived individually", func(t *testing.T) {
|
||||||
t.Run("not editable", func(t *testing.T) {
|
t.Run("not editable", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
|
||||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
|
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
|
||||||
})
|
})
|
||||||
t.Run("unarchivable", func(t *testing.T) {
|
t.Run("unarchivable", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||||
|
|
@ -91,6 +96,7 @@ func TestHumaArchived(t *testing.T) {
|
||||||
|
|
||||||
// Archiving a non-archived project should work.
|
// Archiving a non-archived project should work.
|
||||||
t.Run("archive non-archived project", func(t *testing.T) {
|
t.Run("archive non-archived project", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Test1","is_archived":true}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Test1","is_archived":true}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"is_archived":true`)
|
assert.Contains(t, rec.Body.String(), `"is_archived":true`)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/db"
|
"code.vikunja.io/api/pkg/db"
|
||||||
"code.vikunja.io/api/pkg/models"
|
"code.vikunja.io/api/pkg/models"
|
||||||
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -43,16 +44,19 @@ const runelength250Title = `Lorem ipsum dolor sit amet, consetetur sadipscing el
|
||||||
// codes, absent ETag, expand=permissions). Status-code differences from v1 are
|
// codes, absent ETag, expand=permissions). Status-code differences from v1 are
|
||||||
// noted inline. All cases drive testuser1, whose share fixtures (projects 6–17,
|
// noted inline. All cases drive testuser1, whose share fixtures (projects 6–17,
|
||||||
// 20, 32–34, 9–11) exercise every share-kind×level just like the v1 test.
|
// 20, 32–34, 9–11) exercise every share-kind×level just like the v1 test.
|
||||||
|
//
|
||||||
|
// Each subtest builds its own handler via handlerFor so it runs against freshly
|
||||||
|
// loaded fixtures (setupTestEnv reloads them once per handler). The shared
|
||||||
|
// serve() does not reload per request, so mutating and exact-cardinality
|
||||||
|
// subtests must not share a handler — mirrors huma_team_test.go's isolation.
|
||||||
func TestHumaProject(t *testing.T) {
|
func TestHumaProject(t *testing.T) {
|
||||||
testHandler := webHandlerTestV2{
|
handlerFor := func(u *user.User) *webHandlerTestV2 {
|
||||||
user: &testuser1,
|
return &webHandlerTestV2{user: u, basePath: "/api/v2/projects", idParam: "project", t: t}
|
||||||
basePath: "/api/v2/projects",
|
|
||||||
idParam: "project",
|
|
||||||
t: t,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("ReadAll", func(t *testing.T) {
|
t.Run("ReadAll", func(t *testing.T) {
|
||||||
t.Run("Normal", func(t *testing.T) {
|
t.Run("Normal", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadAllWithUser(nil, nil)
|
rec, err := testHandler.testReadAllWithUser(nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||||
|
|
@ -64,6 +68,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly
|
assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly
|
||||||
})
|
})
|
||||||
t.Run("Search", func(t *testing.T) {
|
t.Run("Search", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"q": []string{"Test1"}}, nil)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"q": []string{"Test1"}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||||
|
|
@ -94,6 +99,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("Normal with archived projects", func(t *testing.T) {
|
t.Run("Normal with archived projects", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||||
|
|
@ -119,6 +125,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.True(t, found21, "Project 21 should be present when listing archived projects")
|
assert.True(t, found21, "Project 21 should be present when listing archived projects")
|
||||||
})
|
})
|
||||||
t.Run("Expand permissions", func(t *testing.T) {
|
t.Run("Expand permissions", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"expand": []string{"permissions"}}, nil)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"expand": []string{"permissions"}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// User 1 owns Test1 → admin (2). With expand the field carries a real value.
|
// User 1 owns Test1 → admin (2). With expand the field carries a real value.
|
||||||
|
|
@ -128,6 +135,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("ReadOne", func(t *testing.T) {
|
t.Run("ReadOne", func(t *testing.T) {
|
||||||
t.Run("Normal", func(t *testing.T) {
|
t.Run("Normal", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "1"})
|
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "1"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||||
|
|
@ -148,6 +156,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
t.Run("Nonexisting", func(t *testing.T) {
|
t.Run("Nonexisting", func(t *testing.T) {
|
||||||
// Projects return 404 here (CanRead → GetProjectSimpleByID → ErrProjectDoesNotExist),
|
// Projects return 404 here (CanRead → GetProjectSimpleByID → ErrProjectDoesNotExist),
|
||||||
// unlike labels which return 403 from the read branch.
|
// unlike labels which return 403 from the read branch.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
|
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||||
|
|
@ -156,6 +165,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
t.Run("Permissions check", func(t *testing.T) {
|
t.Run("Permissions check", func(t *testing.T) {
|
||||||
t.Run("Forbidden", func(t *testing.T) {
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
// Project 20 exists but is owned by user13: CanRead returns false → 403.
|
// Project 20 exists but is owned by user13: CanRead returns false → 403.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
|
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
|
|
@ -165,6 +175,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
// granted level via the always-present max_permission field, the v2
|
// granted level via the always-present max_permission field, the v2
|
||||||
// equivalent of v1's x-max-permission header assertion.
|
// equivalent of v1's x-max-permission header assertion.
|
||||||
readOneWithMaxPermission := func(t *testing.T, projectID, title string, want models.Permission) {
|
readOneWithMaxPermission := func(t *testing.T, projectID, title string, want models.Permission) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": projectID})
|
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": projectID})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"`+title+`"`)
|
assert.Contains(t, rec.Body.String(), `"title":"`+title+`"`)
|
||||||
|
|
@ -218,6 +229,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Create", func(t *testing.T) {
|
t.Run("Create", func(t *testing.T) {
|
||||||
t.Run("Normal", func(t *testing.T) {
|
t.Run("Normal", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem"}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -232,6 +244,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.Contains(t, rec.Body.String(), `"max_permission":null`)
|
assert.Contains(t, rec.Body.String(), `"max_permission":null`)
|
||||||
})
|
})
|
||||||
t.Run("Normal with description", func(t *testing.T) {
|
t.Run("Normal with description", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","description":"Ipsum"}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","description":"Ipsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -241,6 +254,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
||||||
})
|
})
|
||||||
t.Run("Nonexisting parent project", func(t *testing.T) {
|
t.Run("Nonexisting parent project", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":99999}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":99999}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||||
|
|
@ -248,6 +262,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("Empty title", func(t *testing.T) {
|
t.Run("Empty title", func(t *testing.T) {
|
||||||
// v2 returns 422, not v1's 400; full body shape asserted in TestHuma_ErrorShapeIsRFC9457.
|
// v2 returns 422, not v1's 400; full body shape asserted in TestHuma_ErrorShapeIsRFC9457.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
||||||
|
|
@ -255,6 +270,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
t.Run("Title too long", func(t *testing.T) {
|
t.Run("Title too long", func(t *testing.T) {
|
||||||
// v1 hit govalidator runelength(1|250); v2 enforces maxLength:250 at
|
// v1 hit govalidator runelength(1|250); v2 enforces maxLength:250 at
|
||||||
// the schema layer → 422 before the handler.
|
// the schema layer → 422 before the handler.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"`+runelength250Title+`"}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"`+runelength250Title+`"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
||||||
|
|
@ -264,17 +280,20 @@ func TestHumaProject(t *testing.T) {
|
||||||
// write access to that parent.
|
// write access to that parent.
|
||||||
t.Run("Forbidden", func(t *testing.T) {
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
// Parent 20 is owned by user13; user1 has no access.
|
// Parent 20 is owned by user13; user1 has no access.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
|
||||||
// Read-only on parent 32 is not enough to create a child.
|
// Read-only on parent 32 is not enough to create a child.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -284,6 +303,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":34}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":34}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -295,11 +315,13 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
|
||||||
// Read-only on parent 9 is not enough to create a child.
|
// Read-only on parent 9 is not enough to create a child.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
|
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -309,6 +331,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":11}`)
|
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":11}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
|
@ -322,6 +345,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Update", func(t *testing.T) {
|
t.Run("Update", func(t *testing.T) {
|
||||||
t.Run("Normal", func(t *testing.T) {
|
t.Run("Normal", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
|
|
@ -331,23 +355,27 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.Contains(t, rec.Body.String(), `"max_permission":null`)
|
assert.Contains(t, rec.Body.String(), `"max_permission":null`)
|
||||||
})
|
})
|
||||||
t.Run("Normal with updating the description", func(t *testing.T) {
|
t.Run("Normal with updating the description", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
|
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
|
||||||
})
|
})
|
||||||
t.Run("Nonexisting", func(t *testing.T) {
|
t.Run("Nonexisting", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||||
})
|
})
|
||||||
t.Run("Empty title", func(t *testing.T) {
|
t.Run("Empty title", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Title too long", func(t *testing.T) {
|
t.Run("Title too long", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"`+runelength250Title+`"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"`+runelength250Title+`"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusUnprocessableEntity, getHTTPErrorCode(err))
|
||||||
|
|
@ -355,6 +383,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
t.Run("Permissions check", func(t *testing.T) {
|
t.Run("Permissions check", func(t *testing.T) {
|
||||||
t.Run("Forbidden", func(t *testing.T) {
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
// Owned by user13.
|
// Owned by user13.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
|
|
@ -362,64 +391,76 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||||
// Read access is not enough to update.
|
// Read access is not enough to update.
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via User write", func(t *testing.T) {
|
t.Run("Shared Via User write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
|
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
|
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
})
|
})
|
||||||
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
|
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum"}`)
|
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum"}`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||||
|
|
@ -429,6 +470,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("Delete", func(t *testing.T) {
|
||||||
t.Run("Normal", func(t *testing.T) {
|
t.Run("Normal", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1"})
|
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// v2 delete is 204 No Content; v1 returned 200 + a message body.
|
// v2 delete is 204 No Content; v1 returned 200 + a message body.
|
||||||
|
|
@ -436,6 +478,7 @@ func TestHumaProject(t *testing.T) {
|
||||||
assert.Empty(t, rec.Body.String())
|
assert.Empty(t, rec.Body.String())
|
||||||
})
|
})
|
||||||
t.Run("Nonexisting", func(t *testing.T) {
|
t.Run("Nonexisting", func(t *testing.T) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "999"})
|
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "999"})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||||
|
|
@ -444,11 +487,13 @@ func TestHumaProject(t *testing.T) {
|
||||||
t.Run("Permissions check", func(t *testing.T) {
|
t.Run("Permissions check", func(t *testing.T) {
|
||||||
// Delete needs admin everywhere: read and write must be refused, admin allowed.
|
// Delete needs admin everywhere: read and write must be refused, admin allowed.
|
||||||
deleteForbidden := func(t *testing.T, projectID string) {
|
deleteForbidden := func(t *testing.T, projectID string) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": projectID})
|
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": projectID})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
assert.Equal(t, http.StatusForbidden, getHTTPErrorCode(err))
|
||||||
}
|
}
|
||||||
deleteAllowed := func(t *testing.T, projectID string) {
|
deleteAllowed := func(t *testing.T, projectID string) {
|
||||||
|
testHandler := handlerFor(&testuser1)
|
||||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": projectID})
|
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": projectID})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusNoContent, rec.Code)
|
assert.Equal(t, http.StatusNoContent, rec.Code)
|
||||||
|
|
|
||||||
|
|
@ -447,12 +447,6 @@ func (h *webHandlerTestV2) buildURL(queryParams url.Values, urlParams map[string
|
||||||
|
|
||||||
func (h *webHandlerTestV2) serve(method, path, payload string) (*httptest.ResponseRecorder, error) {
|
func (h *webHandlerTestV2) serve(method, path, payload string) (*httptest.ResponseRecorder, error) {
|
||||||
require.NoError(h.t, h.ensureEnv())
|
require.NoError(h.t, h.ensureEnv())
|
||||||
// Reload fixtures before every request so each subtest sees a pristine
|
|
||||||
// database, mirroring v1's webHandlerTest (which calls setupTestEnv ->
|
|
||||||
// LoadFixtures per request via bootstrapTestRequest). Without this, mutating
|
|
||||||
// subtests (create/update/delete) would leak state into later ones in the
|
|
||||||
// shared Echo instance and break permission-matrix assertions.
|
|
||||||
require.NoError(h.t, db.LoadFixtures())
|
|
||||||
token, err := auth.NewUserJWTAuthtoken(h.user, "test-session-id")
|
token, err := auth.NewUserJWTAuthtoken(h.user, "test-session-id")
|
||||||
require.NoError(h.t, err)
|
require.NoError(h.t, err)
|
||||||
var reader *strings.Reader
|
var reader *strings.Reader
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue