vikunja/pkg/webtests/huma_admin_pro_features_tes...

265 lines
10 KiB
Go

// 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 <https://www.gnu.org/licenses/>.
package webtests
import (
"encoding/json"
"net/http"
"testing"
"code.vikunja.io/api/pkg/license"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type proFeatureStateBody struct {
Feature string `json:"feature"`
Licensed bool `json:"licensed"`
PerUserToggleable bool `json:"per_user_toggleable"`
DefaultEnabled bool `json:"default_enabled"`
DefaultSource string `json:"default_source"`
}
type userProFeatureStateBody struct {
Feature string `json:"feature"`
Override *bool `json:"override"`
Effective bool `json:"effective"`
}
func findProFeature(t *testing.T, states []proFeatureStateBody, feature string) proFeatureStateBody {
t.Helper()
for _, st := range states {
if st.Feature == feature {
return st
}
}
t.Fatalf("feature %s not in response: %v", feature, states)
return proFeatureStateBody{}
}
func TestHumaAdminProFeatures(t *testing.T) {
t.Run("non-admin user gets 404", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel})
defer license.ResetForTests()
res := adminReq(t, e, http.MethodGet, "/api/v2/admin/pro-features", &testuser1, "")
assert.Equal(t, http.StatusNotFound, res.Code)
})
t.Run("list reports license state, toggleability and defaults", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodGet, "/api/v2/admin/pro-features", admin, "")
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
var states []proFeatureStateBody
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
tt := findProFeature(t, states, "time_tracking")
assert.True(t, tt.Licensed)
assert.True(t, tt.PerUserToggleable)
assert.True(t, tt.DefaultEnabled)
assert.Equal(t, "code", tt.DefaultSource)
ap := findProFeature(t, states, "admin_panel")
assert.True(t, ap.Licensed)
assert.False(t, ap.PerUserToggleable)
})
t.Run("setting and resetting the instance default", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/pro-features/time_tracking", admin, `{"default_enabled": false}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
var states []proFeatureStateBody
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
tt := findProFeature(t, states, "time_tracking")
assert.False(t, tt.DefaultEnabled)
assert.Equal(t, "instance", tt.DefaultSource)
res = adminReq(t, e, http.MethodDelete, "/api/v2/admin/pro-features/time_tracking", admin, "")
require.Equal(t, http.StatusNoContent, res.Code, res.Body.String())
res = adminReq(t, e, http.MethodGet, "/api/v2/admin/pro-features", admin, "")
require.Equal(t, http.StatusOK, res.Code)
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
tt = findProFeature(t, states, "time_tracking")
assert.True(t, tt.DefaultEnabled)
assert.Equal(t, "code", tt.DefaultSource)
})
t.Run("an instance-wide feature rejects a default", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/pro-features/admin_panel", admin, `{"default_enabled": false}`)
assert.Equal(t, http.StatusUnprocessableEntity, res.Code, res.Body.String())
})
t.Run("an unknown feature is a 404", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/pro-features/nope", admin, `{"default_enabled": false}`)
assert.Equal(t, http.StatusNotFound, res.Code, res.Body.String())
})
}
func TestHumaAdminUserProFeatures(t *testing.T) {
t.Run("set, list and clear an override", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
// Revoke for user 2.
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/users/2/pro-features/time_tracking", admin, `{"enabled": false}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
var states []userProFeatureStateBody
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
require.Len(t, states, 1)
assert.Equal(t, "time_tracking", states[0].Feature)
require.NotNil(t, states[0].Override)
assert.False(t, *states[0].Override)
assert.False(t, states[0].Effective)
res = adminReq(t, e, http.MethodGet, "/api/v2/admin/users/2/pro-features", admin, "")
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
require.Len(t, states, 1)
require.NotNil(t, states[0].Override)
assert.False(t, *states[0].Override)
// Clearing the override falls back to the (code) default: enabled.
res = adminReq(t, e, http.MethodDelete, "/api/v2/admin/users/2/pro-features/time_tracking", admin, "")
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
require.Len(t, states, 1)
assert.Nil(t, states[0].Override)
assert.True(t, states[0].Effective)
})
t.Run("an override wins over the instance default", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/pro-features/time_tracking", admin, `{"default_enabled": false}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
res = adminReq(t, e, http.MethodPut, "/api/v2/admin/users/2/pro-features/time_tracking", admin, `{"enabled": true}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
var states []userProFeatureStateBody
require.NoError(t, json.Unmarshal(res.Body.Bytes(), &states))
require.Len(t, states, 1)
assert.True(t, states[0].Effective)
})
t.Run("a missing user is a 404", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 1)
res := adminReq(t, e, http.MethodGet, "/api/v2/admin/users/9999/pro-features", admin, "")
assert.Equal(t, http.StatusNotFound, res.Code, res.Body.String())
})
t.Run("non-admin user gets 404", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/users/2/pro-features/time_tracking", &testuser1, `{"enabled": false}`)
assert.Equal(t, http.StatusNotFound, res.Code)
})
}
// The per-user gate: a revoked user gets 404s on every time-tracking route
// while other users keep access.
func TestHumaTimeEntry_PerUserGate(t *testing.T) {
t.Run("revoked user is gated, others are not", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 6)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/users/1/pro-features/time_tracking", admin, `{"enabled": false}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
rec := humaRequest(t, e, http.MethodGet, "/api/v2/time-entries", "", humaTokenFor(t, &testuser1), "")
assert.Equal(t, http.StatusNotFound, rec.Code, "revoked user must get a 404")
rec = humaRequest(t, e, http.MethodGet, "/api/v2/time-entries", "", humaTokenFor(t, &testuser2), "")
assert.Equal(t, http.StatusOK, rec.Code, "other users keep access: %s", rec.Body.String())
})
t.Run("disabled instance default gates everyone but granted users", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
license.SetForTests([]license.Feature{license.FeatureAdminPanel, license.FeatureTimeTracking})
defer license.ResetForTests()
admin := promoteToAdmin(t, 6)
res := adminReq(t, e, http.MethodPut, "/api/v2/admin/pro-features/time_tracking", admin, `{"default_enabled": false}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
res = adminReq(t, e, http.MethodPut, "/api/v2/admin/users/1/pro-features/time_tracking", admin, `{"enabled": true}`)
require.Equal(t, http.StatusOK, res.Code, res.Body.String())
rec := humaRequest(t, e, http.MethodGet, "/api/v2/time-entries", "", humaTokenFor(t, &testuser1), "")
assert.Equal(t, http.StatusOK, rec.Code, "granted user keeps access: %s", rec.Body.String())
rec = humaRequest(t, e, http.MethodGet, "/api/v2/time-entries", "", humaTokenFor(t, &testuser2), "")
assert.Equal(t, http.StatusNotFound, rec.Code, "non-granted user must get a 404")
})
}