Improve test suite (#628)
Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>
هذا الالتزام موجود في:
@@ -2,6 +2,12 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/thomiceli/opengist/internal/auth/webauthn"
|
"github.com/thomiceli/opengist/internal/auth/webauthn"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
@@ -12,11 +18,6 @@ import (
|
|||||||
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
||||||
"github.com/thomiceli/opengist/internal/web/server"
|
"github.com/thomiceli/opengist/internal/web/server"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var CmdVersion = cli.Command{
|
var CmdVersion = cli.Command{
|
||||||
@@ -37,7 +38,7 @@ var CmdStart = cli.Command{
|
|||||||
|
|
||||||
Initialize(ctx)
|
Initialize(ctx)
|
||||||
|
|
||||||
httpServer := server.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions"), false)
|
httpServer := server.NewServer(os.Getenv("OG_DEV") == "1")
|
||||||
go httpServer.Start()
|
go httpServer.Start()
|
||||||
go ssh.Start()
|
go ssh.Start()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/session"
|
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/thomiceli/opengist/internal/session"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|||||||
46
internal/web/handlers/admin/actions_test.go
Normal file
46
internal/web/handlers/admin/actions_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package admin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdminActions(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
urls := []string{
|
||||||
|
"/admin-panel/sync-fs",
|
||||||
|
"/admin-panel/sync-db",
|
||||||
|
"/admin-panel/gc-repos",
|
||||||
|
"/admin-panel/sync-previews",
|
||||||
|
"/admin-panel/reset-hooks",
|
||||||
|
"/admin-panel/index-gists",
|
||||||
|
"/admin-panel/sync-languages",
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("NoUser", func(t *testing.T) {
|
||||||
|
for _, url := range urls {
|
||||||
|
s.Request(t, "POST", url, nil, 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for _, url := range urls {
|
||||||
|
resp := s.Request(t, "POST", url, nil, 302)
|
||||||
|
require.Equal(t, "/admin-panel", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonAdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "nonadmin")
|
||||||
|
for _, url := range urls {
|
||||||
|
s.Request(t, "POST", url, nil, 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
269
internal/web/handlers/admin/admin_test.go
Normal file
269
internal/web/handlers/admin/admin_test.go
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
package admin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdminPages(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
urls := []string{
|
||||||
|
"/admin-panel",
|
||||||
|
"/admin-panel/users",
|
||||||
|
"/admin-panel/gists",
|
||||||
|
"/admin-panel/invitations",
|
||||||
|
"/admin-panel/configuration",
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("NoUser", func(t *testing.T) {
|
||||||
|
for _, url := range urls {
|
||||||
|
s.Request(t, "GET", url, nil, 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for _, url := range urls {
|
||||||
|
s.Request(t, "GET", url, nil, 200)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonAdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "nonadmin")
|
||||||
|
for _, url := range urls {
|
||||||
|
s.Request(t, "GET", url, nil, 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminSetConfig(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
settings := []string{
|
||||||
|
db.SettingDisableSignup,
|
||||||
|
db.SettingRequireLogin,
|
||||||
|
db.SettingAllowGistsWithoutLogin,
|
||||||
|
db.SettingDisableLoginForm,
|
||||||
|
db.SettingDisableGravatar,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("NoUser", func(t *testing.T) {
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {db.SettingDisableSignup}, "value": {"1"}}, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonAdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "nonadmin")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {db.SettingDisableSignup}, "value": {"1"}}, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AdminUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
for _, setting := range settings {
|
||||||
|
val, err := db.GetSetting(setting)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0", val)
|
||||||
|
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {setting}, "value": {"1"}}, 200)
|
||||||
|
|
||||||
|
val, err = db.GetSetting(setting)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "1", val)
|
||||||
|
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {setting}, "value": {"0"}}, 200)
|
||||||
|
|
||||||
|
val, err = db.GetSetting(setting)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0", val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminPagination(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
for i := 0; i < 11; i++ {
|
||||||
|
s.Register(t, "user"+strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Pagination", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/admin-panel/users", nil, 200)
|
||||||
|
s.Request(t, "GET", "/admin-panel/users?page=2", nil, 200)
|
||||||
|
s.Request(t, "GET", "/admin-panel/users?page=3", nil, 404)
|
||||||
|
s.Request(t, "GET", "/admin-panel/users?page=0", nil, 200)
|
||||||
|
s.Request(t, "GET", "/admin-panel/users?page=-1", nil, 200)
|
||||||
|
s.Request(t, "GET", "/admin-panel/users?page=a", nil, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUserOperations(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("DeleteUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "nonadmin")
|
||||||
|
|
||||||
|
gist1 := db.GistDTO{
|
||||||
|
Title: "gist",
|
||||||
|
VisibilityDTO: db.VisibilityDTO{
|
||||||
|
Private: 0,
|
||||||
|
},
|
||||||
|
Name: []string{"gist1.txt"},
|
||||||
|
Content: []string{"yeah"},
|
||||||
|
Topics: "",
|
||||||
|
}
|
||||||
|
s.Request(t, "POST", "/", gist1, 302)
|
||||||
|
|
||||||
|
_, err := os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, "nonadmin"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
count, err := db.CountAll(db.User{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(2), count)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/users/2/delete", nil, 404)
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/users/2/delete", nil, 302)
|
||||||
|
|
||||||
|
count, err = db.CountAll(db.User{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), count)
|
||||||
|
|
||||||
|
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, "nonadmin"))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminGistOperations(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("DeleteGist", func(t *testing.T) {
|
||||||
|
s.Login(t, "nonadmin")
|
||||||
|
gist1 := db.GistDTO{
|
||||||
|
Title: "gist",
|
||||||
|
VisibilityDTO: db.VisibilityDTO{
|
||||||
|
Private: 0,
|
||||||
|
},
|
||||||
|
Name: []string{"gist1.txt"},
|
||||||
|
Content: []string{"yeah"},
|
||||||
|
Topics: "",
|
||||||
|
}
|
||||||
|
s.Request(t, "POST", "/", gist1, 302)
|
||||||
|
|
||||||
|
count, err := db.CountAll(db.Gist{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), count)
|
||||||
|
|
||||||
|
gist1Db, err := db.GetGistByID("1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, "nonadmin", gist1Db.Identifier()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/gists/1/delete", nil, 404)
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/gists/1/delete", nil, 302)
|
||||||
|
|
||||||
|
count, err = db.CountAll(db.Gist{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(0), count)
|
||||||
|
|
||||||
|
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, "nonadmin", gist1Db.Identifier()))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminInvitationOperations(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "nonadmin")
|
||||||
|
|
||||||
|
t.Run("Invitation", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations", url.Values{
|
||||||
|
"nbMax": {""},
|
||||||
|
"expiredAtUnix": {""},
|
||||||
|
}, 302)
|
||||||
|
invitation1, err := db.GetInvitationByID(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint(1), invitation1.ID)
|
||||||
|
require.Equal(t, uint(0), invitation1.NbUsed)
|
||||||
|
require.Equal(t, uint(10), invitation1.NbMax)
|
||||||
|
require.InDelta(t, time.Now().Unix()+604800, invitation1.ExpiresAt, 10)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations", url.Values{
|
||||||
|
"nbMax": {"aa"},
|
||||||
|
"expiredAtUnix": {"1735722000"},
|
||||||
|
}, 302)
|
||||||
|
invitation2, err := db.GetInvitationByID(2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, invitation2, &db.Invitation{
|
||||||
|
ID: 2,
|
||||||
|
Code: invitation2.Code,
|
||||||
|
ExpiresAt: time.Unix(1735722000, 0).Unix(),
|
||||||
|
NbUsed: 0,
|
||||||
|
NbMax: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations", url.Values{
|
||||||
|
"nbMax": {"20"},
|
||||||
|
"expiredAtUnix": {"1735722000"},
|
||||||
|
}, 302)
|
||||||
|
invitation3, err := db.GetInvitationByID(3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, invitation3, &db.Invitation{
|
||||||
|
ID: 3,
|
||||||
|
Code: invitation3.Code,
|
||||||
|
ExpiresAt: time.Unix(1735722000, 0).Unix(),
|
||||||
|
NbUsed: 0,
|
||||||
|
NbMax: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
count, err := db.CountAll(db.Invitation{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(3), count)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations/1/delete", nil, 302)
|
||||||
|
|
||||||
|
count, err = db.CountAll(db.Invitation{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(2), count)
|
||||||
|
})
|
||||||
|
}
|
||||||
1
internal/web/handlers/auth/auth_test.go
Normal file
1
internal/web/handlers/auth/auth_test.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package auth_test
|
||||||
221
internal/web/handlers/auth/password_test.go
Normal file
221
internal/web/handlers/auth/password_test.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package auth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegisterPage(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("Form", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/register", nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"isLoginPage": false,
|
||||||
|
"disableForm": false,
|
||||||
|
"disableSignup": false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FormDisabled", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-signup"}, "value": {"1"}}, 200)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/register", nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"disableSignup": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FormDisabledWithInviteCode", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-signup"}, "value": {"1"}}, 200)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations", url.Values{
|
||||||
|
"nbMax": {"10"},
|
||||||
|
"expiredAtUnix": {""},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
invitation, err := db.GetInvitationByID(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/register", nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"disableSignup": true,
|
||||||
|
})
|
||||||
|
s.Request(t, "GET", "/register?code="+invitation.Code, nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"disableSignup": false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessRegister(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Register", func(t *testing.T) {
|
||||||
|
user, err := db.GetUserByUsername("thomas")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, user.IsAdmin)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "seconduser", Password: "password123"}, 302)
|
||||||
|
user, err = db.GetUserByUsername("seconduser")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, user.IsAdmin)
|
||||||
|
s.Logout()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DuplicateUsername", func(t *testing.T) {
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "useraaa", Password: "password123"}, 302)
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "useraaa", Password: "password456"}, 200)
|
||||||
|
s.Logout()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidUsername", func(t *testing.T) {
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "", Password: "password123"}, 200)
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "aze@", Password: "password123"}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EmptyPassword", func(t *testing.T) {
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "newuser", Password: ""}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RegisterDisabled", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-signup"}, "value": {"1"}}, 200)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "blocked", Password: "password123"}, 403)
|
||||||
|
|
||||||
|
exists, err := db.UserExists("blocked")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, exists)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RegisterWithInvitationCode", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-signup"}, "value": {"1"}}, 200)
|
||||||
|
s.Request(t, "POST", "/admin-panel/invitations", url.Values{
|
||||||
|
"nbMax": {"10"},
|
||||||
|
"expiredAtUnix": {""},
|
||||||
|
}, 302)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
invitations, err := db.GetAllInvitations()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, invitations)
|
||||||
|
invitation := invitations[len(invitations)-1]
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/register?code="+invitation.Code, db.UserDTO{Username: "inviteduser", Password: "password123"}, 302)
|
||||||
|
|
||||||
|
user, err := db.GetUserByUsername("inviteduser")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "inviteduser", user.Username)
|
||||||
|
|
||||||
|
updatedInvitation, err := db.GetInvitationByID(invitation.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint(1), updatedInvitation.NbUsed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginPage(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("Form", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/login", nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"isLoginPage": true,
|
||||||
|
"disableForm": false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FormDisabled", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-login-form"}, "value": {"1"}}, 200)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/login", nil, 200)
|
||||||
|
s.TestCtxData(t, echo.Map{
|
||||||
|
"disableForm": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessLogin(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("ValidCredentials", func(t *testing.T) {
|
||||||
|
resp := s.Request(t, "POST", "/login", db.UserDTO{Username: "thomas", Password: "thomas"}, 302)
|
||||||
|
require.Equal(t, "/", resp.Header.Get("Location"))
|
||||||
|
require.NotEmpty(t, s.SessionCookie)
|
||||||
|
require.Equal(t, "thomas", s.User().Username)
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidPassword", func(t *testing.T) {
|
||||||
|
resp := s.Request(t, "POST", "/login", db.UserDTO{Username: "thomas", Password: "wrongpassword"}, 302)
|
||||||
|
require.Equal(t, "/login", resp.Header.Get("Location"))
|
||||||
|
require.Nil(t, s.User())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentUser", func(t *testing.T) {
|
||||||
|
resp := s.Request(t, "POST", "/login", db.UserDTO{Username: "nonexistent", Password: "password"}, 302)
|
||||||
|
require.Equal(t, "/login", resp.Header.Get("Location"))
|
||||||
|
require.Nil(t, s.User())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EmptyCredentials", func(t *testing.T) {
|
||||||
|
s.Request(t, "POST", "/login", db.UserDTO{Username: "", Password: ""}, 302)
|
||||||
|
require.Nil(t, s.User())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LoginFormDisabled", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {"disable-login-form"}, "value": {"1"}}, 200)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/login", db.UserDTO{Username: "thomas", Password: "thomas"}, 403)
|
||||||
|
require.Nil(t, s.User())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogout(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("LogoutRedirects", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
require.Equal(t, "thomas", s.User().Username)
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/logout", nil, 302)
|
||||||
|
require.Equal(t, "/all", resp.Header.Get("Location"))
|
||||||
|
require.Nil(t, s.User())
|
||||||
|
s.Request(t, "GET", "/", nil, 302)
|
||||||
|
})
|
||||||
|
}
|
||||||
519
internal/web/handlers/gist/create_test.go
Normal file
519
internal/web/handlers/gist/create_test.go
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper function to extract username and gist identifier from redirect URL
|
||||||
|
func getGistInfoFromRedirect(resp *http.Response) (username, identifier string) {
|
||||||
|
location := resp.Header.Get("Location")
|
||||||
|
// Location format: /{username}/{identifier}
|
||||||
|
parts := strings.Split(strings.TrimPrefix(location, "/"), "/")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyGistCreation(t *testing.T, gist *db.Gist, username, identifier string) {
|
||||||
|
require.NotNil(t, gist)
|
||||||
|
require.Equal(t, username, gist.User.Username)
|
||||||
|
require.Equal(t, identifier, gist.Identifier())
|
||||||
|
require.NotEmpty(t, gist.Uuid)
|
||||||
|
require.Greater(t, gist.NbFiles, 0)
|
||||||
|
|
||||||
|
gistPath := filepath.Join(config.GetHomeDir(), git.ReposDirectory, username, gist.Uuid)
|
||||||
|
_, err := os.Stat(gistPath)
|
||||||
|
require.NoError(t, err, "Gist repository should exist on filesystem at %s", gistPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGistCreationPage(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/", nil, 302)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Authenticated", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "GET", "/", nil, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGistCreation(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
s.Request(t, "POST", "/", url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello world"},
|
||||||
|
}, 302) // Redirects to login
|
||||||
|
|
||||||
|
// Verify no gist was created
|
||||||
|
count, err := db.CountAll(db.Gist{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(0), count)
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data url.Values
|
||||||
|
expectedCode int
|
||||||
|
expectGistCreated bool
|
||||||
|
expectedTitle string
|
||||||
|
expectedDescription string
|
||||||
|
expectedURL string
|
||||||
|
expectedTopics string // Expected topics string
|
||||||
|
expectedNbFiles int
|
||||||
|
expectedVisibility db.Visibility
|
||||||
|
expectedFileNames []string // Expected filenames in the gist
|
||||||
|
expectedFileContents map[string]string // Expected content for each file (filename -> content)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoFiles",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyContent",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {""},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TitleTooLong",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {strings.Repeat("a", 251)}, // Max is 250
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DescriptionTooLong",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"description": {strings.Repeat("a", 1001)}, // Max is 1000
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLTooLong",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"url": {strings.Repeat("a", 33)}, // Max is 32
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLInvalidCharacters",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"url": {"invalid@url#here"}, // Only alphanumeric and dashes allowed
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "InvalidVisibility",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test Gist"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"private": {"3"}, // Valid values are 0, 1, 2
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"My Test Gist"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"url": {"my-custom-url-123"}, // Alphanumeric + dashes should be valid
|
||||||
|
"content": {"hello world"},
|
||||||
|
"private": {"0"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "My Test Gist",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"test.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"test.txt": "hello world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AutoNamedFile",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Auto Named"},
|
||||||
|
"name": {""},
|
||||||
|
"content": {"content without name"},
|
||||||
|
"private": {"0"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Auto Named",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"gistfile1.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"gistfile1.txt": "content without name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFiles",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Multi File Gist"},
|
||||||
|
"name": []string{"", "file2.md"},
|
||||||
|
"content": []string{"content 1", "content 2"},
|
||||||
|
"private": {"0"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Multi File Gist",
|
||||||
|
expectedNbFiles: 2,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"gistfile1.txt", "file2.md"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"gistfile1.txt": "content 1",
|
||||||
|
"file2.md": "content 2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoTitle",
|
||||||
|
data: url.Values{
|
||||||
|
"name": {"readme.md"},
|
||||||
|
"content": {"# README"},
|
||||||
|
"private": {"0"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "readme.md",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"readme.md"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"readme.md": "# README",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unlisted",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Unlisted Gist"},
|
||||||
|
"name": {"secret.txt"},
|
||||||
|
"content": {"secret content"},
|
||||||
|
"private": {"1"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Unlisted Gist",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.UnlistedVisibility,
|
||||||
|
expectedFileNames: []string{"secret.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"secret.txt": "secret content",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Private Gist"},
|
||||||
|
"name": {"secret.txt"},
|
||||||
|
"content": {"secret content"},
|
||||||
|
"private": {"2"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Private Gist",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PrivateVisibility,
|
||||||
|
expectedFileNames: []string{"secret.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"secret.txt": "secret content",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Topics",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Gist With Topics"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"topics": {"golang testing webdev"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Gist With Topics",
|
||||||
|
expectedTopics: "golang,testing,webdev",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"test.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"test.txt": "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TopicsTooMany",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"topics": {"topic1 topic2 topic3 topic4 topic5 topic6 topic7 topic8 topic9 topic10 topic11"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TopicTooLong",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"topics": {strings.Repeat("a", 51)}, // 51 chars - exceeds max of 50
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TopicInvalidCharacters",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"topics": {"topic@name topic.name"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TopicUnicode",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Unicode Topics"},
|
||||||
|
"name": {"test.txt"},
|
||||||
|
"content": {"hello"},
|
||||||
|
"topics": {"编程 тест"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Unicode Topics",
|
||||||
|
expectedTopics: "编程,тест",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"test.txt"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DuplicateFileNames",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Duplicate Files"},
|
||||||
|
"name": []string{"test.txt", "test.txt"},
|
||||||
|
"content": []string{"content1", "content2"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Duplicate Files",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"test.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"test.txt": "content2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FileNameTooLong",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Too Long Filename"},
|
||||||
|
"name": {strings.Repeat("a", 256) + ".txt"}, // 260 total - exceeds 255
|
||||||
|
"content": {"hello"},
|
||||||
|
},
|
||||||
|
expectedCode: 400,
|
||||||
|
expectGistCreated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FileNameWithUnicode",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Unicode Filename"},
|
||||||
|
"name": {"文件.txt"},
|
||||||
|
"content": {"hello world"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Unicode Filename",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"文件.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"文件.txt": "hello world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FileNamePathTraversal",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Path Traversal"},
|
||||||
|
"name": {"../../../etc/passwd"},
|
||||||
|
"content": {"malicious"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Path Traversal",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"passwd"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"passwd": "malicious",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyAndValidContent",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Mixed Content"},
|
||||||
|
"name": []string{"empty.txt", "valid.txt", "also-empty.txt"},
|
||||||
|
"content": []string{"", "valid content", ""},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Mixed Content",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"valid.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"valid.txt": "valid content",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ContentWithSpecialCharacters",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Special Chars"},
|
||||||
|
"name": {"special.txt"},
|
||||||
|
"content": {"Line1\nLine2\tTabbed\x00NullByte😀Emoji"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Special Chars",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"special.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"special.txt": "Line1\nLine2\tTabbed\x00NullByte😀Emoji",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ContentMultibyteUnicode",
|
||||||
|
data: url.Values{
|
||||||
|
"title": {"Unicode Content"},
|
||||||
|
"name": {"unicode.txt"},
|
||||||
|
"content": {"Hello 世界 🌍 Привет"},
|
||||||
|
},
|
||||||
|
expectedCode: 302,
|
||||||
|
expectGistCreated: true,
|
||||||
|
expectedTitle: "Unicode Content",
|
||||||
|
expectedNbFiles: 1,
|
||||||
|
expectedVisibility: db.PublicVisibility,
|
||||||
|
expectedFileNames: []string{"unicode.txt"},
|
||||||
|
expectedFileContents: map[string]string{
|
||||||
|
"unicode.txt": "Hello 世界 🌍 Привет",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
resp := s.Request(t, "POST", "/", tt.data, tt.expectedCode)
|
||||||
|
|
||||||
|
if tt.expectGistCreated {
|
||||||
|
// Get gist info from redirect
|
||||||
|
username, gistIdentifier := getGistInfoFromRedirect(resp)
|
||||||
|
require.Equal(t, "thomas", username)
|
||||||
|
require.NotEmpty(t, gistIdentifier)
|
||||||
|
|
||||||
|
// Verify gist was created
|
||||||
|
gist, err := db.GetGist(username, gistIdentifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Run common verification (filesystem, git, etc.)
|
||||||
|
verifyGistCreation(t, gist, username, gistIdentifier)
|
||||||
|
|
||||||
|
// Verify all expected fields
|
||||||
|
require.Equal(t, tt.expectedTitle, gist.Title, "Title mismatch")
|
||||||
|
require.Equal(t, tt.expectedNbFiles, gist.NbFiles, "File count mismatch")
|
||||||
|
require.Equal(t, tt.expectedVisibility, gist.Private, "Visibility mismatch")
|
||||||
|
|
||||||
|
// Verify description if specified
|
||||||
|
if tt.expectedDescription != "" {
|
||||||
|
require.Equal(t, tt.expectedDescription, gist.Description, "Description mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify URL if specified
|
||||||
|
if tt.expectedURL != "" {
|
||||||
|
require.Equal(t, tt.expectedURL, gist.Identifier(), "URL/Identifier mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify topics if specified
|
||||||
|
if tt.expectedTopics != "" {
|
||||||
|
// Get gist topics
|
||||||
|
topics, err := gist.GetTopics()
|
||||||
|
require.NoError(t, err, "Failed to get gist topics")
|
||||||
|
require.ElementsMatch(t, strings.Split(tt.expectedTopics, ","), topics, "Topics mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify files if specified
|
||||||
|
if len(tt.expectedFileNames) > 0 {
|
||||||
|
files, err := gist.Files("HEAD", false)
|
||||||
|
require.NoError(t, err, "Failed to get gist files")
|
||||||
|
require.Len(t, files, len(tt.expectedFileNames), "File count mismatch")
|
||||||
|
|
||||||
|
actualFileNames := make([]string, len(files))
|
||||||
|
for i, file := range files {
|
||||||
|
actualFileNames[i] = file.Filename
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t, tt.expectedFileNames, actualFileNames, "File names mismatch")
|
||||||
|
|
||||||
|
// Verify file contents if specified
|
||||||
|
if len(tt.expectedFileContents) > 0 {
|
||||||
|
for filename, expectedContent := range tt.expectedFileContents {
|
||||||
|
content, _, err := git.GetFileContent(username, gist.Uuid, "HEAD", filename, false)
|
||||||
|
require.NoError(t, err, "Failed to get content for file %s", filename)
|
||||||
|
require.Equal(t, expectedContent, content, "Content mismatch for file %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
74
internal/web/handlers/gist/delete_test.go
Normal file
74
internal/web/handlers/gist/delete_test.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeleteGist(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
gistPath, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
deleteURL := "/" + username + "/" + identifier + "/delete"
|
||||||
|
s.Request(t, "POST", deleteURL, nil, 302)
|
||||||
|
|
||||||
|
gistCheck, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err, "Gist should still exist in database")
|
||||||
|
require.NotNil(t, gistCheck)
|
||||||
|
|
||||||
|
_, err = os.Stat(gistPath)
|
||||||
|
require.NoError(t, err, "Gist should still exist on filesystem")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteOwnGist", func(t *testing.T) {
|
||||||
|
gistPath, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
gistCheck, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, gistCheck)
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
deleteURL := "/" + username + "/" + identifier + "/delete"
|
||||||
|
s.Request(t, "POST", deleteURL, nil, 302)
|
||||||
|
|
||||||
|
gistCheck, err = db.GetGist(username, identifier)
|
||||||
|
require.Error(t, err, "Gist should be deleted from database")
|
||||||
|
|
||||||
|
_, err = os.Stat(gistPath)
|
||||||
|
require.Error(t, err, "Gist should not exist on filesystem after deletion")
|
||||||
|
require.True(t, os.IsNotExist(err), "Filesystem should return 'not exist' error")
|
||||||
|
require.Equal(t, uint(0), gistCheck.ID, "Gist should be not in database after deletion")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteOthersGist", func(t *testing.T) {
|
||||||
|
gistPath, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
deleteURL := "/" + username + "/" + identifier + "/delete"
|
||||||
|
s.Request(t, "POST", deleteURL, nil, 403)
|
||||||
|
|
||||||
|
gistCheck, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err, "Gist should still exist in database")
|
||||||
|
require.NotNil(t, gistCheck)
|
||||||
|
|
||||||
|
_, err = os.Stat(gistPath)
|
||||||
|
require.NoError(t, err, "Gist should still exist on filesystem")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteNonExistentGist", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
deleteURL := "/thomas/nonexistent-gist-12345/delete"
|
||||||
|
s.Request(t, "POST", deleteURL, nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
141
internal/web/handlers/gist/download_test.go
Normal file
141
internal/web/handlers/gist/download_test.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDownloadZip(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
t.Run("MultipleFiles", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+"/archive/HEAD", nil, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, zipReader.File, 2)
|
||||||
|
|
||||||
|
fileNames := make([]string, len(zipReader.File))
|
||||||
|
contents := make([]string, len(zipReader.File))
|
||||||
|
for i, file := range zipReader.File {
|
||||||
|
fileNames[i] = file.Name
|
||||||
|
f, err := file.Open()
|
||||||
|
require.NoError(t, err)
|
||||||
|
content, err := io.ReadAll(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
contents[i] = string(content)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t, []string{"file.txt", "otherfile.txt"}, fileNames)
|
||||||
|
require.ElementsMatch(t, []string{"hello world", "other content"}, contents)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/archive/HEAD", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentRevision", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
// TODO: return 404
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/archive/zz", nil, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRawFile(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
t.Run("ExistingFile", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+"/raw/HEAD/file.txt", nil, 200)
|
||||||
|
|
||||||
|
require.Equal(t, `inline; filename="file.txt"`, resp.Header.Get("Content-Disposition"))
|
||||||
|
require.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
|
||||||
|
require.Contains(t, resp.Header.Get("Content-Type"), "text/plain")
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "hello world", string(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentFile", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/raw/HEAD/nonexistent.txt", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentRevision", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/raw/zz/file.txt", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/raw/HEAD/file.txt", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadFile(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
t.Run("ExistingFile", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+"/download/HEAD/file.txt", nil, 200)
|
||||||
|
|
||||||
|
require.Equal(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
|
||||||
|
require.Equal(t, `attachment; filename="file.txt"`, resp.Header.Get("Content-Disposition"))
|
||||||
|
require.Equal(t, "11", resp.Header.Get("Content-Length"))
|
||||||
|
require.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "hello world", string(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentFile", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+"/download/HEAD/nonexistent.txt", nil, 404)
|
||||||
|
|
||||||
|
_, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// TODO: change the response to not found
|
||||||
|
// require.Equal(t, "File not found", string(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentRevision", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+"/download/zz/file.txt", nil, 404)
|
||||||
|
|
||||||
|
_, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// TODO: change the response to not found
|
||||||
|
// require.Equal(t, "File not found", string(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/download/HEAD/file.txt", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package gist
|
package gist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/render"
|
"github.com/thomiceli/opengist/internal/render"
|
||||||
"github.com/thomiceli/opengist/internal/web/context"
|
"github.com/thomiceli/opengist/internal/web/context"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Edit(ctx *context.Context) error {
|
func Edit(ctx *context.Context) error {
|
||||||
|
|||||||
66
internal/web/handlers/gist/edit_test.go
Normal file
66
internal/web/handlers/gist/edit_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVisibility(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("ChangeVisibility", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/visibility", url.Values{
|
||||||
|
"private": {"2"},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, db.PrivateVisibility, gist.Private)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ChangeToUnlisted", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/visibility", url.Values{
|
||||||
|
"private": {"1"},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, db.UnlistedVisibility, gist.Private)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUserCannotChange", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/visibility", nil, 403)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, db.PublicVisibility, gist.Private)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/visibility", nil, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, db.PublicVisibility, gist.Private)
|
||||||
|
})
|
||||||
|
}
|
||||||
102
internal/web/handlers/gist/fork_test.go
Normal file
102
internal/web/handlers/gist/fork_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFork(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Fork", func(t *testing.T) {
|
||||||
|
_, gist, username, identifier := s.CreateGist(t, "0")
|
||||||
|
s.Login(t, "alice")
|
||||||
|
|
||||||
|
resp := s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 302)
|
||||||
|
|
||||||
|
forkedGist, err := db.GetGistByID("2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "alice", forkedGist.User.Username)
|
||||||
|
require.Equal(t, gist.Title, forkedGist.Title)
|
||||||
|
require.Equal(t, gist.Description, forkedGist.Description)
|
||||||
|
require.Equal(t, gist.Private, forkedGist.Private)
|
||||||
|
require.Equal(t, gist.ID, forkedGist.ForkedID)
|
||||||
|
|
||||||
|
forkedFiles, err := forkedGist.Files("HEAD", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gistFiles, err := gist.Files("HEAD", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, file := range gistFiles {
|
||||||
|
require.Equal(t, file.Filename, forkedFiles[i].Filename)
|
||||||
|
require.Equal(t, file.Content, forkedFiles[i].Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "/alice/"+forkedGist.Identifier(), resp.Header.Get("Location"))
|
||||||
|
|
||||||
|
original, err := db.GetGistByID("1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, original.NbForks)
|
||||||
|
|
||||||
|
forks, err := original.GetForks(2, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, forks, 1)
|
||||||
|
require.Equal(t, forkedGist.ID, forks[0].ID)
|
||||||
|
|
||||||
|
forkedGists, err := db.GetAllGistsForkedByUser(2, 2, 0, "created", "asc")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, forkedGists, 1)
|
||||||
|
require.Equal(t, forkedGist.ID, forkedGists[0].ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OwnGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 302)
|
||||||
|
|
||||||
|
original, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, original.NbForks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AlreadyForked", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
s.Login(t, "alice")
|
||||||
|
|
||||||
|
firstResp := s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 302)
|
||||||
|
forkLocation := firstResp.Header.Get("Location")
|
||||||
|
|
||||||
|
secondResp := s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 302)
|
||||||
|
require.Equal(t, forkLocation, secondResp.Header.Get("Location"))
|
||||||
|
|
||||||
|
original, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, original.NbForks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 302)
|
||||||
|
|
||||||
|
original, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, original.NbForks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
s.Login(t, "alice")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/fork", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
293
internal/web/handlers/gist/gist_test.go
Normal file
293
internal/web/handlers/gist/gist_test.go
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
"github.com/thomiceli/opengist/internal/web/context"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupManifestEntries() {
|
||||||
|
context.ManifestEntries = map[string]context.Asset{
|
||||||
|
"embed.css": {File: "assets/embed.css"},
|
||||||
|
"ts/embed.ts": {Css: []string{"assets/embed.css"}},
|
||||||
|
"ts/light.ts": {Css: []string{"assets/light.css"}},
|
||||||
|
"ts/dark.ts": {Css: []string{"assets/dark.css"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGistIndex(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Public", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentRevision", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/rev/nonexistent", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentGist", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/thomas/nonexistent", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Unlisted", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "1")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 200)
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 200)
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Private", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 200)
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 404)
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier, nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SpecificRevision", func(t *testing.T) {
|
||||||
|
_, gist, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/edit", url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"file.txt"},
|
||||||
|
"content": {"updated content"},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
files, err := gist.Files("HEAD", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
found := false
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Filename == "file.txt" {
|
||||||
|
require.Equal(t, "updated content", f.Content)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, found)
|
||||||
|
|
||||||
|
commits, err := gist.Log(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, commits, 2)
|
||||||
|
|
||||||
|
filesOld, err := gist.Files(commits[1].Hash, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, f := range filesOld {
|
||||||
|
if f.Filename == "file.txt" {
|
||||||
|
require.Equal(t, "hello world", f.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/rev/HEAD", nil, 200)
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/rev/"+commits[1].Hash, nil, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreview(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("Markdown", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
resp := s.Request(t, "POST", "/preview", url.Values{
|
||||||
|
"content": {"# Hello\n\nThis is **bold** and *italic*."},
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
html := string(body)
|
||||||
|
require.Contains(t, html, "<h1>")
|
||||||
|
require.Contains(t, html, "Hello")
|
||||||
|
require.Contains(t, html, "<strong>bold</strong>")
|
||||||
|
require.Contains(t, html, "<em>italic</em>")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "POST", "/preview", url.Values{
|
||||||
|
"content": {"# Hello"},
|
||||||
|
}, 302)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGistJson(t *testing.T) {
|
||||||
|
setupManifestEntries()
|
||||||
|
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Public", func(t *testing.T) {
|
||||||
|
_, gist, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
resp := s.Request(t, "GET", "/"+username+"/"+identifier+".json", nil, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.Equal(t, username, result["owner"])
|
||||||
|
require.Equal(t, identifier, result["id"])
|
||||||
|
require.Equal(t, gist.Uuid, result["uuid"])
|
||||||
|
require.Equal(t, gist.Title, result["title"])
|
||||||
|
require.Equal(t, "public", result["visibility"])
|
||||||
|
require.Equal(t, []interface{}{"hello", "opengist"}, result["topics"])
|
||||||
|
require.Equal(t, []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"content": "hello world",
|
||||||
|
"filename": "file.txt",
|
||||||
|
"human_size": "11 B",
|
||||||
|
"size": float64(11),
|
||||||
|
"truncated": false,
|
||||||
|
"type": "Text",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"content": "other content",
|
||||||
|
"filename": "otherfile.txt",
|
||||||
|
"human_size": "13 B",
|
||||||
|
"size": float64(13),
|
||||||
|
"truncated": false,
|
||||||
|
"type": "Text",
|
||||||
|
},
|
||||||
|
}, result["files"])
|
||||||
|
|
||||||
|
embed, ok := result["embed"].(map[string]interface{})
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Contains(t, embed["js"], identifier+".js")
|
||||||
|
require.Contains(t, embed["js_dark"], identifier+".js?dark")
|
||||||
|
require.NotEmpty(t, embed["css"])
|
||||||
|
require.NotEmpty(t, embed["html"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Unlisted", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "1")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+".json", nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Private", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+".json", nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NonExistentGist", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/thomas/nonexistent.json", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGistAccess(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
_, _, user, publicId := s.CreateGist(t, "0")
|
||||||
|
_, _, _, unlistedId := s.CreateGist(t, "1")
|
||||||
|
_, _, _, privateId := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
settings map[string]string
|
||||||
|
// expected codes: [owner, otherUser, anonymous] x [public, unlisted, private]
|
||||||
|
owner, otherUser, anonymous []int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
owner: []int{200, 200, 200},
|
||||||
|
otherUser: []int{200, 200, 404},
|
||||||
|
anonymous: []int{200, 200, 404},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RequireLogin",
|
||||||
|
settings: map[string]string{db.SettingRequireLogin: "1"},
|
||||||
|
owner: []int{200, 200, 200},
|
||||||
|
otherUser: []int{200, 200, 404},
|
||||||
|
anonymous: []int{302, 302, 302},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AllowGistsWithoutLogin",
|
||||||
|
settings: map[string]string{db.SettingRequireLogin: "1", db.SettingAllowGistsWithoutLogin: "1"},
|
||||||
|
owner: []int{200, 200, 200},
|
||||||
|
otherUser: []int{200, 200, 404},
|
||||||
|
anonymous: []int{200, 200, 404},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gists := []string{publicId, unlistedId, privateId}
|
||||||
|
labels := []string{"Public", "Unlisted", "Private"}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for k, v := range tt.settings {
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {k}, "value": {v}}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Owner", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for i, id := range gists {
|
||||||
|
s.Request(t, "GET", "/"+user+"/"+id, nil, tt.owner[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUser", func(t *testing.T) {
|
||||||
|
s.Login(t, "alice")
|
||||||
|
for i, id := range gists {
|
||||||
|
s.Request(t, "GET", "/"+user+"/"+id, nil, tt.otherUser[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Anonymous", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
for i, id := range gists {
|
||||||
|
t.Run(labels[i], func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/"+user+"/"+id, nil, tt.anonymous[i])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for k := range tt.settings {
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {k}, "value": {"0"}}, 200)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
96
internal/web/handlers/gist/like_test.go
Normal file
96
internal/web/handlers/gist/like_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLike(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Like", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
resp := s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
require.Equal(t, "/"+username+"/"+identifier, resp.Header.Get("Location"))
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, gist.NbLikes)
|
||||||
|
|
||||||
|
likers, err := gist.GetUsersLikes(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, likers, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Unlike", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, gist.NbLikes)
|
||||||
|
|
||||||
|
likers, err := gist.GetUsersLikes(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, likers, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
|
||||||
|
gist, err := db.GetGist(username, identifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, gist.NbLikes)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLikes(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Likes", func(t *testing.T) {
|
||||||
|
_, gist, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/like", nil, 302)
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/likes", nil, 200)
|
||||||
|
|
||||||
|
users, err := gist.GetUsersLikes(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users, 2)
|
||||||
|
require.Equal(t, "thomas", users[0].Username)
|
||||||
|
require.Equal(t, "alice", users[1].Username)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package gist
|
package gist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/web/context"
|
"github.com/thomiceli/opengist/internal/web/context"
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers"
|
"github.com/thomiceli/opengist/internal/web/handlers"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Revisions(ctx *context.Context) error {
|
func Revisions(ctx *context.Context) error {
|
||||||
|
|||||||
153
internal/web/handlers/gist/revisions_test.go
Normal file
153
internal/web/handlers/gist/revisions_test.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRevisions(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
t.Run("Revisions", func(t *testing.T) {
|
||||||
|
_, gist, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/edit", url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"file.txt", "ok.txt"},
|
||||||
|
"content": {"updated content", "okay"},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/"+username+"/"+identifier+"/edit", url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"renamed.txt", "ok.txt"},
|
||||||
|
"content": {"updated content", "okay"},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
commits, err := gist.Log(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, commits, 3)
|
||||||
|
|
||||||
|
require.Regexp(t, "^[a-f0-9]{40}$", commits[0].Hash)
|
||||||
|
require.Regexp(t, "^[a-f0-9]{40}$", commits[1].Hash)
|
||||||
|
require.Regexp(t, "^[a-f0-9]{40}$", commits[2].Hash)
|
||||||
|
|
||||||
|
require.Equal(t, &git.Commit{
|
||||||
|
Hash: commits[0].Hash,
|
||||||
|
Timestamp: commits[0].Timestamp,
|
||||||
|
AuthorName: "thomas",
|
||||||
|
Changed: "1 file changed, 0 insertions, 0 deletions",
|
||||||
|
Files: []git.File{
|
||||||
|
{
|
||||||
|
Filename: "renamed.txt",
|
||||||
|
Size: 0,
|
||||||
|
HumanSize: "",
|
||||||
|
OldFilename: "file.txt",
|
||||||
|
Content: ``,
|
||||||
|
Truncated: false,
|
||||||
|
IsCreated: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
IsBinary: false,
|
||||||
|
MimeType: git.MimeType{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, commits[0])
|
||||||
|
|
||||||
|
require.Equal(t, &git.Commit{
|
||||||
|
Hash: commits[1].Hash,
|
||||||
|
Timestamp: commits[1].Timestamp,
|
||||||
|
AuthorName: "thomas",
|
||||||
|
Changed: "3 files changed, 2 insertions, 2 deletions",
|
||||||
|
Files: []git.File{
|
||||||
|
{
|
||||||
|
Filename: "file.txt",
|
||||||
|
OldFilename: "file.txt",
|
||||||
|
Content: `@@ -1 +1 @@
|
||||||
|
-hello world
|
||||||
|
\ No newline at end of file
|
||||||
|
+updated content
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
IsCreated: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
IsBinary: false,
|
||||||
|
}, {
|
||||||
|
Filename: "ok.txt",
|
||||||
|
OldFilename: "",
|
||||||
|
Content: `@@ -0,0 +1 @@
|
||||||
|
+okay
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
IsCreated: true,
|
||||||
|
IsDeleted: false,
|
||||||
|
IsBinary: false,
|
||||||
|
}, {
|
||||||
|
Filename: "otherfile.txt",
|
||||||
|
OldFilename: "",
|
||||||
|
Content: `@@ -1 +0,0 @@
|
||||||
|
-other content
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
IsCreated: false,
|
||||||
|
IsDeleted: true,
|
||||||
|
IsBinary: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, commits[1])
|
||||||
|
|
||||||
|
require.Equal(t, &git.Commit{
|
||||||
|
Hash: commits[2].Hash,
|
||||||
|
Timestamp: commits[2].Timestamp,
|
||||||
|
AuthorName: "thomas",
|
||||||
|
Changed: "2 files changed, 2 insertions",
|
||||||
|
Files: []git.File{
|
||||||
|
{
|
||||||
|
Filename: "file.txt",
|
||||||
|
OldFilename: "",
|
||||||
|
Content: `@@ -0,0 +1 @@
|
||||||
|
+hello world
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
IsCreated: true,
|
||||||
|
IsDeleted: false,
|
||||||
|
IsBinary: false,
|
||||||
|
}, {
|
||||||
|
Filename: "otherfile.txt",
|
||||||
|
OldFilename: "",
|
||||||
|
Content: `@@ -0,0 +1 @@
|
||||||
|
+other content
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
IsCreated: true,
|
||||||
|
IsDeleted: false,
|
||||||
|
IsBinary: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, commits[2])
|
||||||
|
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/revisions", nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/revisions", nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateGist", func(t *testing.T) {
|
||||||
|
_, _, username, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Login(t, "alice")
|
||||||
|
s.Request(t, "GET", "/"+username+"/"+identifier+"/revisions", nil, 404)
|
||||||
|
})
|
||||||
|
}
|
||||||
151
internal/web/handlers/gist/upload_test.go
Normal file
151
internal/web/handlers/gist/upload_test.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package gist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createMultipartRequest(t *testing.T, uri, fieldName, fileName, content string) *http.Request {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile(fieldName, fileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = part.Write([]byte(content))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, writer.Close())
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, uri, body)
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpload(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("UploadFile", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := createMultipartRequest(t, "/upload", "file", "test.txt", "file content")
|
||||||
|
|
||||||
|
resp := s.RawRequest(t, req, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var result map[string]string
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "test.txt", result["filename"])
|
||||||
|
require.NotEmpty(t, result["uuid"])
|
||||||
|
require.Regexp(t, `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`, result["uuid"])
|
||||||
|
|
||||||
|
filePath := filepath.Join(config.GetHomeDir(), "uploads", result["uuid"])
|
||||||
|
data, err := os.ReadFile(filePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "file content", string(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoFile", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/upload", nil)
|
||||||
|
req.Header.Set("Content-Type", "multipart/form-data; boundary=xxx")
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
req := createMultipartRequest(t, "/upload", "file", "test.txt", "content")
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 302)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUpload(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("DeleteExistingFile", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := createMultipartRequest(t, "/upload", "file", "todelete.txt", "delete me")
|
||||||
|
|
||||||
|
uploadResp := s.RawRequest(t, req, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(uploadResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var uploadResult map[string]string
|
||||||
|
err = json.Unmarshal(body, &uploadResult)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileUUID := uploadResult["uuid"]
|
||||||
|
|
||||||
|
filePath := filepath.Join(config.GetHomeDir(), "uploads", fileUUID)
|
||||||
|
_, err = os.Stat(filePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
deleteReq := httptest.NewRequest(http.MethodDelete, "/upload/"+fileUUID, nil)
|
||||||
|
|
||||||
|
deleteResp := s.RawRequest(t, deleteReq, 200)
|
||||||
|
|
||||||
|
deleteBody, err := io.ReadAll(deleteResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var deleteResult map[string]string
|
||||||
|
err = json.Unmarshal(deleteBody, &deleteResult)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "deleted", deleteResult["status"])
|
||||||
|
|
||||||
|
_, err = os.Stat(filePath)
|
||||||
|
require.True(t, os.IsNotExist(err))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteNonExistentFile", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/upload/00000000-0000-0000-0000-000000000000", nil)
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidUUID", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/upload/not-a-valid-uuid", nil)
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PathTraversal", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/upload/../../etc/passwd", nil)
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAuth", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/upload/00000000-0000-0000-0000-000000000000", nil)
|
||||||
|
|
||||||
|
s.RawRequest(t, req, 302)
|
||||||
|
})
|
||||||
|
}
|
||||||
235
internal/web/handlers/git/http_test.go
Normal file
235
internal/web/handlers/git/http_test.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package git_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gitClone(baseUrl, creds, user, gistId, destDir string) error {
|
||||||
|
authUrl := baseUrl
|
||||||
|
if creds != "" {
|
||||||
|
authUrl = "http://" + creds + "@" + baseUrl[len("http://"):]
|
||||||
|
}
|
||||||
|
return exec.Command("git", "clone", authUrl+"/"+user+"/"+gistId+".git", destDir).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitPush(repoDir, filename, content string) error {
|
||||||
|
if err := os.WriteFile(filepath.Join(repoDir, filename), []byte(content), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := exec.Command("git", "-C", repoDir, "add", filename).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := exec.Command("git", "-C", repoDir, "commit", "-m", "add "+filename).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return exec.Command("git", "-C", repoDir, "push", "origin").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitClonePull(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
baseUrl := s.StartHttpServer(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
_, _, user, publicId := s.CreateGist(t, "0")
|
||||||
|
_, _, _, unlistedId := s.CreateGist(t, "1")
|
||||||
|
_, _, _, privateId := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
type credTest struct {
|
||||||
|
name string
|
||||||
|
creds string
|
||||||
|
expect [3]bool // [public, unlisted, private]
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
settings map[string]string
|
||||||
|
creds []credTest
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
creds: []credTest{
|
||||||
|
{"OwnerAuth", "thomas:thomas", [3]bool{true, true, true}},
|
||||||
|
{"OtherUserAuth", "alice:alice", [3]bool{true, true, false}},
|
||||||
|
{"WrongPassword", "thomas:wrong", [3]bool{true, true, false}},
|
||||||
|
{"WrongUser", "aze:aze", [3]bool{true, true, false}},
|
||||||
|
{"Anonymous", "", [3]bool{true, true, false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RequireLogin",
|
||||||
|
settings: map[string]string{db.SettingRequireLogin: "1"},
|
||||||
|
creds: []credTest{
|
||||||
|
{"OwnerAuth", "thomas:thomas", [3]bool{true, true, true}},
|
||||||
|
{"OtherUserAuth", "alice:alice", [3]bool{true, true, false}},
|
||||||
|
{"WrongPassword", "thomas:wrong", [3]bool{false, false, false}},
|
||||||
|
{"WrongUser", "aze:aze", [3]bool{false, false, false}},
|
||||||
|
{"Anonymous", "", [3]bool{false, false, false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AllowGistsWithoutLogin",
|
||||||
|
settings: map[string]string{db.SettingRequireLogin: "1", db.SettingAllowGistsWithoutLogin: "1"},
|
||||||
|
creds: []credTest{
|
||||||
|
{"OwnerAuth", "thomas:thomas", [3]bool{true, true, true}},
|
||||||
|
{"OtherUserAuth", "alice:alice", [3]bool{true, true, false}},
|
||||||
|
{"WrongPassword", "thomas:wrong", [3]bool{true, true, false}},
|
||||||
|
{"WrongUser", "aze:aze", [3]bool{true, true, false}},
|
||||||
|
{"Anonymous", "", [3]bool{true, true, false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gists := [3]string{publicId, unlistedId, privateId}
|
||||||
|
labels := [3]string{"Public", "Unlisted", "Private"}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for k, v := range tt.settings {
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {k}, "value": {v}}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ct := range tt.creds {
|
||||||
|
t.Run(ct.name, func(t *testing.T) {
|
||||||
|
for i, id := range gists {
|
||||||
|
t.Run(labels[i], func(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
err := gitClone(baseUrl, ct.creds, user, id, dest)
|
||||||
|
if ct.expect[i] {
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = os.Stat(filepath.Join(dest, "file.txt"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset settings
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
for k := range tt.settings {
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {k}, "value": {"0"}}, 200)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitPush(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
baseUrl := s.StartHttpServer(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
_, _, user, publicId := s.CreateGist(t, "0")
|
||||||
|
_, _, _, unlistedId := s.CreateGist(t, "1")
|
||||||
|
_, _, _, privateId := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
type credTest struct {
|
||||||
|
name string
|
||||||
|
creds string
|
||||||
|
expect [3]bool // [public, unlisted, private]
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []credTest{
|
||||||
|
{"OwnerAuth", "thomas:thomas", [3]bool{true, true, true}},
|
||||||
|
{"OtherUserAuth", "alice:alice", [3]bool{false, false, false}},
|
||||||
|
{"WrongPassword", "thomas:wrong", [3]bool{false, false, false}},
|
||||||
|
{"WrongUser", "aze:aze", [3]bool{false, false, false}},
|
||||||
|
{"Anonymous", "", [3]bool{false, false, false}},
|
||||||
|
}
|
||||||
|
|
||||||
|
gists := [3]string{publicId, unlistedId, privateId}
|
||||||
|
labels := [3]string{"Public", "Unlisted", "Private"}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
for i, id := range gists {
|
||||||
|
t.Run(labels[i], func(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
require.NoError(t, gitClone(baseUrl, "thomas:thomas", user, id, dest))
|
||||||
|
|
||||||
|
if tt.creds != "thomas:thomas" {
|
||||||
|
require.NoError(t, exec.Command("git", "-C", dest, "remote", "set-url", "origin",
|
||||||
|
"http://"+tt.creds+"@"+baseUrl[len("http://"):]+"/"+user+"/"+id+".git").Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
err := gitPush(dest, "newfile.txt", "new content")
|
||||||
|
if tt.expect[i] {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitCreatePush(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
baseUrl := s.StartHttpServer(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "alice")
|
||||||
|
|
||||||
|
gitInitAndPush := func(t *testing.T, creds, remoteUrl string) error {
|
||||||
|
dest := t.TempDir()
|
||||||
|
require.NoError(t, exec.Command("git", "init", "--initial-branch=master", dest).Run())
|
||||||
|
require.NoError(t, exec.Command("git", "-C", dest, "remote", "add", "origin",
|
||||||
|
"http://"+creds+"@"+baseUrl[len("http://"):]+remoteUrl).Run())
|
||||||
|
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(dest, "hello.txt"), []byte("hello"), 0644))
|
||||||
|
require.NoError(t, exec.Command("git", "-C", dest, "add", "hello.txt").Run())
|
||||||
|
require.NoError(t, exec.Command("git", "-C", dest, "commit", "-m", "initial").Run())
|
||||||
|
return exec.Command("git", "-C", dest, "push", "origin").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
creds string
|
||||||
|
url string
|
||||||
|
expect bool
|
||||||
|
gistOwner string // if expect=true, verify gist exists at this owner/identifier
|
||||||
|
gistId string
|
||||||
|
}{
|
||||||
|
{"OwnerCreates", "thomas:thomas", "/thomas/mygist.git", true, "thomas", "mygist"},
|
||||||
|
{"OtherUserCreatesOnOwnUrl", "alice:alice", "/alice/alicegist.git", true, "alice", "alicegist"},
|
||||||
|
{"WrongPassword", "thomas:wrong", "/thomas/newgist.git", false, "", ""},
|
||||||
|
{"OtherUserCannotCreateOnOwner", "alice:alice", "/thomas/hackgist.git", false, "", ""},
|
||||||
|
{"WrongUser", "aze:aze", "/aze/azegist.git", false, "", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := gitInitAndPush(t, tt.creds, tt.url)
|
||||||
|
if tt.expect {
|
||||||
|
require.NoError(t, err)
|
||||||
|
gist, err := db.GetGist(tt.gistOwner, tt.gistId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, gist)
|
||||||
|
require.Equal(t, tt.gistId, gist.Identifier())
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
30
internal/web/handlers/health/healthcheck_test.go
Normal file
30
internal/web/handlers/health/healthcheck_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package health_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthcheck(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
resp := s.Request(t, "GET", "/healthcheck", nil, 200)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "ok", result["opengist"])
|
||||||
|
require.Equal(t, "ok", result["database"])
|
||||||
|
require.NotEmpty(t, result["time"])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package test
|
package metrics_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -10,19 +10,17 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func TestMetrics(t *testing.T) {
|
||||||
SSHKey = db.SSHKeyDTO{
|
s := webtest.Setup(t)
|
||||||
Title: "Test SSH Key",
|
defer webtest.Teardown(t)
|
||||||
Content: `ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSUGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XAt3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/EnmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbxNrRFi9wrf+M7Q== admin@admin.local`,
|
|
||||||
}
|
|
||||||
AdminUser = db.UserDTO{
|
|
||||||
Username: "admin",
|
|
||||||
Password: "admin",
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleGist = db.GistDTO{
|
s.Register(t, "thomas")
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
s.Request(t, "POST", "/", db.GistDTO{
|
||||||
Title: "Simple Test Gist",
|
Title: "Simple Test Gist",
|
||||||
Description: "A simple gist for testing",
|
Description: "A simple gist for testing",
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
VisibilityDTO: db.VisibilityDTO{
|
||||||
@@ -31,39 +29,14 @@ var (
|
|||||||
Name: []string{"file1.txt"},
|
Name: []string{"file1.txt"},
|
||||||
Content: []string{"This is the content of file1"},
|
Content: []string{"This is the content of file1"},
|
||||||
Topics: "",
|
Topics: "",
|
||||||
}
|
}, 302)
|
||||||
)
|
|
||||||
|
|
||||||
// TestMetrics tests the metrics endpoint functionality of the application.
|
s.Request(t, "POST", "/settings/ssh-keys", db.SSHKeyDTO{
|
||||||
// It verifies that the metrics endpoint correctly reports counts for:
|
Title: "Test SSH Key",
|
||||||
// - Total number of users
|
Content: `ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSUGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XAt3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/EnmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbxNrRFi9wrf+M7Q== admin@admin.local`,
|
||||||
// - Total number of gists
|
}, 302)
|
||||||
// - Total number of SSH keys
|
|
||||||
//
|
|
||||||
// The test follows these steps:
|
|
||||||
// 1. Sets up test environment
|
|
||||||
// 2. Registers and logs in an admin user
|
|
||||||
// 3. Creates a gist and adds an SSH key
|
|
||||||
// 4. Creates a metrics server and queries the /metrics endpoint
|
|
||||||
// 5. Verifies the reported metrics match expected values
|
|
||||||
func TestMetrics(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
register(t, s, AdminUser)
|
metricsServer := webtest.NewTestMetricsServer()
|
||||||
login(t, s, AdminUser)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/all", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/", SimpleGist, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/settings/ssh-keys", SSHKey, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a metrics server and query it
|
|
||||||
metricsServer := NewTestMetricsServer()
|
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "/metrics", nil)
|
req := httptest.NewRequest("GET", "/metrics", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
332
internal/web/handlers/settings/access_token_test.go
Normal file
332
internal/web/handlers/settings/access_token_test.go
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package settings_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
|
webtest "github.com/thomiceli/opengist/internal/web/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessTokensCRUD(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
|
||||||
|
t.Run("RequiresAuth", func(t *testing.T) {
|
||||||
|
s.Logout()
|
||||||
|
s.Request(t, "GET", "/settings/access-tokens", nil, 302)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AccessTokensPage", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "GET", "/settings/access-tokens", nil, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CreateReadToken", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "POST", "/settings/access-tokens", db.AccessTokenDTO{
|
||||||
|
Name: "test-token",
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
tokens, err := db.GetAccessTokensByUserID(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, tokens, 1)
|
||||||
|
require.Equal(t, "test-token", tokens[0].Name)
|
||||||
|
require.Equal(t, uint(db.ReadPermission), tokens[0].ScopeGist)
|
||||||
|
require.Equal(t, int64(0), tokens[0].ExpiresAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CreateExpiringToken", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
|
||||||
|
s.Request(t, "POST", "/settings/access-tokens", db.AccessTokenDTO{
|
||||||
|
Name: "expiring-token",
|
||||||
|
ScopeGist: db.ReadWritePermission,
|
||||||
|
ExpiresAt: tomorrow,
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
tokens, err := db.GetAccessTokensByUserID(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, tokens, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteToken", func(t *testing.T) {
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
s.Request(t, "DELETE", "/settings/access-tokens/1", nil, 302)
|
||||||
|
|
||||||
|
tokens, err := db.GetAccessTokensByUserID(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, tokens, 1)
|
||||||
|
require.Equal(t, "expiring-token", tokens[0].Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenPrivateGistAccess(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
_, _, user, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
// Create access token with read permission
|
||||||
|
token := &db.AccessToken{
|
||||||
|
Name: "read-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
plainToken, err := token.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, token.Create())
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
headers := map[string]string{"Authorization": "Token " + plainToken}
|
||||||
|
|
||||||
|
t.Run("NoTokenReturns404", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/"+user+"/"+identifier, nil, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ValidTokenGrantsAccess", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 200, headers)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RawContentAccessible", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier+"/raw/HEAD/file.txt", nil, 200, headers)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("JSONEndpointAccessible", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier+".json", nil, 200, headers)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidTokenReturns404", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 404, map[string]string{
|
||||||
|
"Authorization": "Token invalid_token",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenPermissions(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
_, _, user, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
// Create token with NO permission
|
||||||
|
noPermToken := &db.AccessToken{
|
||||||
|
Name: "no-perm-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.NoPermission,
|
||||||
|
}
|
||||||
|
noPermPlain, err := noPermToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, noPermToken.Create())
|
||||||
|
|
||||||
|
// Create token with READ permission
|
||||||
|
readToken := &db.AccessToken{
|
||||||
|
Name: "read-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
readPlain, err := readToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, readToken.Create())
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
t.Run("NoPermissionDenied", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 404, map[string]string{
|
||||||
|
"Authorization": "Token " + noPermPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ReadPermissionGranted", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 200, map[string]string{
|
||||||
|
"Authorization": "Token " + readPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenExpiration(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
_, _, user, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
// Create an expired token
|
||||||
|
expiredToken := &db.AccessToken{
|
||||||
|
Name: "expired-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
ExpiresAt: time.Now().Add(-24 * time.Hour).Unix(),
|
||||||
|
}
|
||||||
|
expiredPlain, err := expiredToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, expiredToken.Create())
|
||||||
|
|
||||||
|
// Create a valid token
|
||||||
|
validToken := &db.AccessToken{
|
||||||
|
Name: "valid-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||||
|
}
|
||||||
|
validPlain, err := validToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, validToken.Create())
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
t.Run("ExpiredTokenDenied", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 404, map[string]string{
|
||||||
|
"Authorization": "Token " + expiredPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ValidTokenGranted", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 200, map[string]string{
|
||||||
|
"Authorization": "Token " + validPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenWrongUser(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
s.Register(t, "kaguya")
|
||||||
|
|
||||||
|
_, _, user, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
// Create token for kaguya
|
||||||
|
kaguyaToken := &db.AccessToken{
|
||||||
|
Name: "kaguya-token",
|
||||||
|
UserID: 2,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
kaguyaPlain, err := kaguyaToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, kaguyaToken.Create())
|
||||||
|
|
||||||
|
// Create token for thomas
|
||||||
|
thomasToken := &db.AccessToken{
|
||||||
|
Name: "thomas-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
thomasPlain, err := thomasToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, thomasToken.Create())
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
t.Run("OtherUserTokenDenied", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 404, map[string]string{
|
||||||
|
"Authorization": "Token " + kaguyaPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OwnerTokenGranted", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 200, map[string]string{
|
||||||
|
"Authorization": "Token " + thomasPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenLastUsedUpdate(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
_, _, user, identifier := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
token := &db.AccessToken{
|
||||||
|
Name: "test-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
plainToken, err := token.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, token.Create())
|
||||||
|
|
||||||
|
// Verify LastUsedAt is 0 initially
|
||||||
|
tokenFromDB, err := db.GetAccessTokenByID(token.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(0), tokenFromDB.LastUsedAt)
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
// Use the token
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user+"/"+identifier, nil, 200, map[string]string{
|
||||||
|
"Authorization": "Token " + plainToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify LastUsedAt was updated
|
||||||
|
tokenFromDB, err = db.GetAccessTokenByID(token.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, int64(0), tokenFromDB.LastUsedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenWithRequireLogin(t *testing.T) {
|
||||||
|
s := webtest.Setup(t)
|
||||||
|
defer webtest.Teardown(t)
|
||||||
|
|
||||||
|
s.Register(t, "thomas")
|
||||||
|
_, _, user1, identifier1 := s.CreateGist(t, "2")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
_, _, user2, identifier2 := s.CreateGist(t, "0")
|
||||||
|
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
token := &db.AccessToken{
|
||||||
|
Name: "read-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.ReadPermission,
|
||||||
|
}
|
||||||
|
plainToken, err := token.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, token.Create())
|
||||||
|
|
||||||
|
s.Request(t, "PUT", "/admin-panel/set-config", url.Values{"key": {db.SettingRequireLogin}, "value": {"1"}}, 200)
|
||||||
|
s.Logout()
|
||||||
|
|
||||||
|
headers := map[string]string{"Authorization": "Token " + plainToken}
|
||||||
|
|
||||||
|
t.Run("UnauthenticatedRedirects", func(t *testing.T) {
|
||||||
|
s.Request(t, "GET", "/"+user1+"/"+identifier1, nil, 302)
|
||||||
|
s.Request(t, "GET", "/"+user2+"/"+identifier2, nil, 302)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ValidTokenGrantsAccess", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user1+"/"+identifier1, nil, 200, headers)
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user2+"/"+identifier2, nil, 200, headers)
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user1+"/"+identifier1+"/raw/HEAD/file.txt", nil, 200, headers)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidTokenRedirects", func(t *testing.T) {
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user1+"/"+identifier1, nil, 302, map[string]string{
|
||||||
|
"Authorization": "Token invalid_token",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoPermTokenRedirects", func(t *testing.T) {
|
||||||
|
noPermToken := &db.AccessToken{
|
||||||
|
Name: "no-perm-token",
|
||||||
|
UserID: 1,
|
||||||
|
ScopeGist: db.NoPermission,
|
||||||
|
}
|
||||||
|
noPermPlain, err := noPermToken.GenerateToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, noPermToken.Create())
|
||||||
|
|
||||||
|
s.RequestWithHeaders(t, "GET", "/"+user1+"/"+identifier1, nil, 302, map[string]string{
|
||||||
|
"Authorization": "Token " + noPermPlain,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
func (s *Server) useCustomContext() {
|
func (s *Server) useCustomContext() {
|
||||||
s.echo.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
s.echo.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
cc := context.NewContext(c, s.sessionsPath)
|
cc := context.NewContext(c, filepath.Join(config.GetHomeDir(), "sessions"))
|
||||||
return next(cc)
|
return next(cc)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -58,29 +58,27 @@ func (s *Server) registerMiddlewares() {
|
|||||||
s.echo.Use(middleware.Recover())
|
s.echo.Use(middleware.Recover())
|
||||||
s.echo.Use(middleware.Secure())
|
s.echo.Use(middleware.Secure())
|
||||||
s.echo.Use(Middleware(sessionInit).toEcho())
|
s.echo.Use(Middleware(sessionInit).toEcho())
|
||||||
|
s.echo.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||||
|
TokenLookup: "form:_csrf,header:X-CSRF-Token",
|
||||||
|
CookiePath: "/",
|
||||||
|
CookieHTTPOnly: true,
|
||||||
|
CookieSameSite: http.SameSiteStrictMode,
|
||||||
|
Skipper: func(ctx echo.Context) bool {
|
||||||
|
/* skip CSRF for embeds */
|
||||||
|
gistName := ctx.Param("gistname")
|
||||||
|
|
||||||
if !s.ignoreCsrf {
|
/* skip CSRF for git clients */
|
||||||
s.echo.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
|
matchUploadPack, _ := regexp.MatchString("(.*?)/git-upload-pack$", ctx.Request().URL.Path)
|
||||||
TokenLookup: "form:_csrf,header:X-CSRF-Token",
|
matchReceivePack, _ := regexp.MatchString("(.*?)/git-receive-pack$", ctx.Request().URL.Path)
|
||||||
CookiePath: "/",
|
return (filepath.Ext(gistName) == ".js" && ctx.Request().Method == "GET") || matchUploadPack || matchReceivePack
|
||||||
CookieHTTPOnly: true,
|
},
|
||||||
CookieSameSite: http.SameSiteStrictMode,
|
ErrorHandler: func(err error, c echo.Context) error {
|
||||||
Skipper: func(ctx echo.Context) bool {
|
log.Info().Err(err).Msg("CSRF error")
|
||||||
/* skip CSRF for embeds */
|
return err
|
||||||
gistName := ctx.Param("gistname")
|
},
|
||||||
|
}))
|
||||||
|
s.echo.Use(Middleware(csrfInit).toEcho())
|
||||||
|
|
||||||
/* skip CSRF for git clients */
|
|
||||||
matchUploadPack, _ := regexp.MatchString("(.*?)/git-upload-pack$", ctx.Request().URL.Path)
|
|
||||||
matchReceivePack, _ := regexp.MatchString("(.*?)/git-receive-pack$", ctx.Request().URL.Path)
|
|
||||||
return (filepath.Ext(gistName) == ".js" && ctx.Request().Method == "GET") || matchUploadPack || matchReceivePack
|
|
||||||
},
|
|
||||||
ErrorHandler: func(err error, c echo.Context) error {
|
|
||||||
log.Info().Err(err).Msg("CSRF error")
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
s.echo.Use(Middleware(csrfInit).toEcho())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) errorHandler(err error, ctx echo.Context) {
|
func (s *Server) errorHandler(err error, ctx echo.Context) {
|
||||||
@@ -159,10 +157,10 @@ func dataInit(next Handler) Handler {
|
|||||||
|
|
||||||
func writePermission(next Handler) Handler {
|
func writePermission(next Handler) Handler {
|
||||||
return func(ctx *context.Context) error {
|
return func(ctx *context.Context) error {
|
||||||
gist := ctx.GetData("gist")
|
gist := ctx.GetData("gist").(*db.Gist)
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
if !gist.(*db.Gist).CanWrite(user) {
|
if !gist.CanWrite(user) {
|
||||||
return ctx.RedirectTo("/" + gist.(*db.Gist).User.Username + "/" + gist.(*db.Gist).Identifier())
|
return ctx.ErrorRes(403, "You don't have permission to edit this gist", nil)
|
||||||
}
|
}
|
||||||
return next(ctx)
|
return next(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/validator"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,6 +9,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/thomiceli/opengist/internal/validator"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
@@ -18,19 +19,16 @@ import (
|
|||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
echo *echo.Echo
|
echo *echo.Echo
|
||||||
|
dev bool
|
||||||
dev bool
|
|
||||||
sessionsPath string
|
|
||||||
ignoreCsrf bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server {
|
func NewServer(isDev bool) *Server {
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
e.HidePort = true
|
e.HidePort = true
|
||||||
e.Validator = validator.NewValidator()
|
e.Validator = validator.NewValidator()
|
||||||
|
|
||||||
s := &Server{echo: e, dev: isDev, sessionsPath: sessionsPath, ignoreCsrf: ignoreCsrf}
|
s := &Server{echo: e, dev: isDev}
|
||||||
|
|
||||||
s.useCustomContext()
|
s.useCustomContext()
|
||||||
|
|
||||||
@@ -175,3 +173,7 @@ func (s *Server) createPidFile(pidPath string) error {
|
|||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
s.echo.ServeHTTP(w, r)
|
s.echo.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Use(middleware echo.MiddlewareFunc) {
|
||||||
|
s.echo.Use(middleware)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,448 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccessTokensCRUD(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register and login
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
// Access tokens page requires login
|
|
||||||
s.sessionCookie = ""
|
|
||||||
err := s.Request("GET", "/settings/access-tokens", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
// Access tokens page
|
|
||||||
err = s.Request("GET", "/settings/access-tokens", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a token with read permission
|
|
||||||
tokenDTO := db.AccessTokenDTO{
|
|
||||||
Name: "test-token",
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/settings/access-tokens", tokenDTO, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify token was created in database
|
|
||||||
tokens, err := db.GetAccessTokensByUserID(1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, tokens, 1)
|
|
||||||
require.Equal(t, "test-token", tokens[0].Name)
|
|
||||||
require.Equal(t, uint(db.ReadPermission), tokens[0].ScopeGist)
|
|
||||||
require.Equal(t, int64(0), tokens[0].ExpiresAt)
|
|
||||||
|
|
||||||
// Create another token with expiration
|
|
||||||
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
|
|
||||||
tokenDTO2 := db.AccessTokenDTO{
|
|
||||||
Name: "expiring-token",
|
|
||||||
ScopeGist: db.ReadWritePermission,
|
|
||||||
ExpiresAt: tomorrow,
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/settings/access-tokens", tokenDTO2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tokens, err = db.GetAccessTokensByUserID(1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, tokens, 2)
|
|
||||||
|
|
||||||
// Delete the first token
|
|
||||||
err = s.Request("DELETE", "/settings/access-tokens/1", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tokens, err = db.GetAccessTokensByUserID(1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, tokens, 1)
|
|
||||||
require.Equal(t, "expiring-token", tokens[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenPrivateGistAccess(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register user and create a private gist
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "private-gist",
|
|
||||||
Description: "my private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"secret.txt"},
|
|
||||||
Content: []string{"secret content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create access token with read permission
|
|
||||||
token := &db.AccessToken{
|
|
||||||
Name: "read-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
plainToken, err := token.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = token.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Clear session - simulate unauthenticated request
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// Without token, private gist should return 404
|
|
||||||
err = s.Request("GET", "/thomas/"+gist1db.Uuid, nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// With valid token, private gist should be accessible
|
|
||||||
headers := map[string]string{"Authorization": "Token " + plainToken}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Raw content should also be accessible with token
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid+"/raw/HEAD/secret.txt", nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// JSON endpoint should also be accessible with token
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid+".json", nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Invalid token should not work
|
|
||||||
invalidHeaders := map[string]string{"Authorization": "Token invalid_token"}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 404, invalidHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenPermissions(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register user and create a private gist
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "private-gist",
|
|
||||||
Description: "my private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"file.txt"},
|
|
||||||
Content: []string{"content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create token with NO permission
|
|
||||||
noPermToken := &db.AccessToken{
|
|
||||||
Name: "no-perm-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.NoPermission,
|
|
||||||
}
|
|
||||||
noPermPlain, err := noPermToken.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = noPermToken.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create token with READ permission
|
|
||||||
readToken := &db.AccessToken{
|
|
||||||
Name: "read-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
readPlain, err := readToken.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = readToken.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// No permission token should not grant access
|
|
||||||
noPermHeaders := map[string]string{"Authorization": "Token " + noPermPlain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 404, noPermHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Read permission token should grant access
|
|
||||||
readHeaders := map[string]string{"Authorization": "Token " + readPlain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, readHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenExpiration(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register user and create a private gist
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "private-gist",
|
|
||||||
Description: "my private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"file.txt"},
|
|
||||||
Content: []string{"content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create an expired token
|
|
||||||
expiredToken := &db.AccessToken{
|
|
||||||
Name: "expired-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
ExpiresAt: time.Now().Add(-24 * time.Hour).Unix(), // Expired yesterday
|
|
||||||
}
|
|
||||||
expiredPlain, err := expiredToken.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = expiredToken.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a valid (non-expired) token
|
|
||||||
validToken := &db.AccessToken{
|
|
||||||
Name: "valid-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), // Expires tomorrow
|
|
||||||
}
|
|
||||||
validPlain, err := validToken.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = validToken.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// Expired token should not grant access
|
|
||||||
expiredHeaders := map[string]string{"Authorization": "Token " + expiredPlain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 404, expiredHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Valid token should grant access
|
|
||||||
validHeaders := map[string]string{"Authorization": "Token " + validPlain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, validHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenWrongUser(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register two users
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
// Create a private gist for user1
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "thomas-private-gist",
|
|
||||||
Description: "thomas private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"file.txt"},
|
|
||||||
Content: []string{"content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
user2 := db.UserDTO{Username: "kaguya", Password: "kaguya"}
|
|
||||||
register(t, s, user2)
|
|
||||||
|
|
||||||
// Create token for user2
|
|
||||||
user2Token := &db.AccessToken{
|
|
||||||
Name: "kaguya-token",
|
|
||||||
UserID: 2,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
user2Plain, err := user2Token.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = user2Token.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// User2's token should NOT grant access to user1's private gist
|
|
||||||
user2Headers := map[string]string{"Authorization": "Token " + user2Plain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 404, user2Headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create token for user1
|
|
||||||
user1Token := &db.AccessToken{
|
|
||||||
Name: "thomas-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
user1Plain, err := user1Token.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = user1Token.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// User1's token SHOULD grant access to user1's private gist
|
|
||||||
user1Headers := map[string]string{"Authorization": "Token " + user1Plain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, user1Headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenLastUsedUpdate(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
// Register user and create a private gist
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "private-gist",
|
|
||||||
Description: "my private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"file.txt"},
|
|
||||||
Content: []string{"content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create token
|
|
||||||
token := &db.AccessToken{
|
|
||||||
Name: "test-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
plainToken, err := token.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = token.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify LastUsedAt is 0 initially
|
|
||||||
tokenFromDB, err := db.GetAccessTokenByID(token.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(0), tokenFromDB.LastUsedAt)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// Use the token
|
|
||||||
headers := map[string]string{"Authorization": "Token " + plainToken}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify LastUsedAt was updated
|
|
||||||
tokenFromDB, err = db.GetAccessTokenByID(token.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEqual(t, int64(0), tokenFromDB.LastUsedAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessTokenWithRequireLogin(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
admin := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, admin)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "private-gist",
|
|
||||||
Description: "my private gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"file.txt"},
|
|
||||||
Content: []string{"content"},
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist2 := db.GistDTO{
|
|
||||||
Title: "public-gist",
|
|
||||||
Description: "my public gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PublicVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"public.txt"},
|
|
||||||
Content: []string{"public content"},
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist2db, err := db.GetGistByID("2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
token := &db.AccessToken{
|
|
||||||
Name: "read-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.ReadPermission,
|
|
||||||
}
|
|
||||||
plainToken, err := token.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = token.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{"require-login", "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
err = s.Request("GET", "/thomas/"+gist1db.Uuid, nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/thomas/"+gist2db.Uuid, nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
headers := map[string]string{"Authorization": "Token " + plainToken}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist2db.Uuid, nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid+"/raw/HEAD/file.txt", nil, 200, headers)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
invalidHeaders := map[string]string{"Authorization": "Token invalid_token"}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 302, invalidHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
noPermToken := &db.AccessToken{
|
|
||||||
Name: "no-perm-token",
|
|
||||||
UserID: 1,
|
|
||||||
ScopeGist: db.NoPermission,
|
|
||||||
}
|
|
||||||
noPermPlain, err := noPermToken.GenerateToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = noPermToken.Create()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
noPermHeaders := map[string]string{"Authorization": "Token " + noPermPlain}
|
|
||||||
err = s.RequestWithHeaders("GET", "/thomas/"+gist1db.Uuid, nil, 302, noPermHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAdminActions(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
urls := []string{
|
|
||||||
"/admin-panel/sync-fs",
|
|
||||||
"/admin-panel/sync-db",
|
|
||||||
"/admin-panel/gc-repos",
|
|
||||||
"/admin-panel/sync-previews",
|
|
||||||
"/admin-panel/reset-hooks",
|
|
||||||
"/admin-panel/index-gists",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("POST", url, nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("POST", url, nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user2 := db.UserDTO{Username: "nonadmin", Password: "nonadmin"}
|
|
||||||
register(t, s, user2)
|
|
||||||
login(t, s, user2)
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("POST", url, nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAdminPages(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
urls := []string{
|
|
||||||
"/admin-panel",
|
|
||||||
"/admin-panel/users",
|
|
||||||
"/admin-panel/gists",
|
|
||||||
"/admin-panel/invitations",
|
|
||||||
"/admin-panel/configuration",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("GET", url, nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("GET", url, nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user2 := db.UserDTO{Username: "nonadmin", Password: "nonadmin"}
|
|
||||||
register(t, s, user2)
|
|
||||||
login(t, s, user2)
|
|
||||||
for _, url := range urls {
|
|
||||||
err := s.Request("GET", url, nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetConfig(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
settings := []string{
|
|
||||||
db.SettingDisableSignup,
|
|
||||||
db.SettingRequireLogin,
|
|
||||||
db.SettingAllowGistsWithoutLogin,
|
|
||||||
db.SettingDisableLoginForm,
|
|
||||||
db.SettingDisableGravatar,
|
|
||||||
}
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
for _, setting := range settings {
|
|
||||||
val, err := db.GetSetting(setting)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "0", val)
|
|
||||||
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{setting, "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
val, err = db.GetSetting(setting)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "1", val)
|
|
||||||
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{setting, "0"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
val, err = db.GetSetting(setting)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "0", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPagination(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
for i := 0; i < 11; i++ {
|
|
||||||
user := db.UserDTO{Username: "user" + strconv.Itoa(i), Password: "user" + strconv.Itoa(i)}
|
|
||||||
register(t, s, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/admin-panel/users", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/admin-panel/users?page=2", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/admin-panel/users?page=3", nil, 404)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/admin-panel/users?page=0", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/admin-panel/users?page=-1", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/admin-panel/users?page=a", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdminUser(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
user2 := db.UserDTO{Username: "nonadmin", Password: "nonadmin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
register(t, s, user2)
|
|
||||||
|
|
||||||
login(t, s, user2)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt"},
|
|
||||||
Content: []string{"yeah"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, user2.Username))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
count, err := db.CountAll(db.User{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(2), count)
|
|
||||||
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/admin-panel/users/2/delete", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
count, err = db.CountAll(db.User{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(1), count)
|
|
||||||
|
|
||||||
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, user2.Username))
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdminGist(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt"},
|
|
||||||
Content: []string{"yeah"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
count, err := db.CountAll(db.Gist{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(1), count)
|
|
||||||
|
|
||||||
gist1Db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, user1.Username, gist1Db.Identifier()))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/admin-panel/gists/1/delete", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
count, err = db.CountAll(db.Gist{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(0), count)
|
|
||||||
|
|
||||||
_, err = os.Stat(filepath.Join(config.GetHomeDir(), git.ReposDirectory, user1.Username, gist1Db.Identifier()))
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdminInvitation(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "admin", Password: "admin"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
err := s.Request("POST", "/admin-panel/invitations", invitationAdmin{
|
|
||||||
nbMax: "",
|
|
||||||
expiredAtUnix: "",
|
|
||||||
}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
invitation1, err := db.GetInvitationByID(1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, uint(1), invitation1.ID)
|
|
||||||
require.Equal(t, uint(0), invitation1.NbUsed)
|
|
||||||
require.Equal(t, uint(10), invitation1.NbMax)
|
|
||||||
require.InDelta(t, time.Now().Unix()+604800, invitation1.ExpiresAt, 10)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/admin-panel/invitations", invitationAdmin{
|
|
||||||
nbMax: "aa",
|
|
||||||
expiredAtUnix: "1735722000",
|
|
||||||
}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
invitation2, err := db.GetInvitationByID(2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, invitation2, &db.Invitation{
|
|
||||||
ID: 2,
|
|
||||||
Code: invitation2.Code,
|
|
||||||
ExpiresAt: time.Unix(1735722000, 0).Unix(),
|
|
||||||
NbUsed: 0,
|
|
||||||
NbMax: 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
err = s.Request("POST", "/admin-panel/invitations", invitationAdmin{
|
|
||||||
nbMax: "20",
|
|
||||||
expiredAtUnix: "1735722000",
|
|
||||||
}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
invitation3, err := db.GetInvitationByID(3)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, invitation3, &db.Invitation{
|
|
||||||
ID: 3,
|
|
||||||
Code: invitation3.Code,
|
|
||||||
ExpiresAt: time.Unix(1735722000, 0).Unix(),
|
|
||||||
NbUsed: 0,
|
|
||||||
NbMax: 20,
|
|
||||||
})
|
|
||||||
|
|
||||||
count, err := db.CountAll(db.Invitation{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(3), count)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/admin-panel/invitations/1/delete", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
count, err = db.CountAll(db.Invitation{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(2), count)
|
|
||||||
}
|
|
||||||
@@ -1,414 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/register", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
user1db, err := db.GetUserById(1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, user1.Username, user1db.Username)
|
|
||||||
require.True(t, user1db.IsAdmin)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
user2 := db.UserDTO{Username: "thomas", Password: "azeaze"}
|
|
||||||
err = s.Request("POST", "/register", user2, 200)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
user3 := db.UserDTO{Username: "kaguya", Password: "kaguya"}
|
|
||||||
register(t, s, user3)
|
|
||||||
|
|
||||||
user3db, err := db.GetUserById(2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, user3db.IsAdmin)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
count, err := db.CountAll(db.User{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(2), count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/login", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
login(t, s, user1)
|
|
||||||
require.NotEmpty(t, s.sessionCookie)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
user2 := db.UserDTO{Username: "thomas", Password: "azeaze"}
|
|
||||||
user3 := db.UserDTO{Username: "azeaze", Password: ""}
|
|
||||||
|
|
||||||
err = s.Request("POST", "/login", user2, 302)
|
|
||||||
require.Empty(t, s.sessionCookie)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/login", user3, 302)
|
|
||||||
require.Empty(t, s.sessionCookie)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(t *testing.T, s *TestServer, user db.UserDTO) {
|
|
||||||
err := s.Request("POST", "/register", user, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func login(t *testing.T, s *TestServer, user db.UserDTO) {
|
|
||||||
err := s.Request("POST", "/login", user, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnonymous(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user := db.UserDTO{Username: "thomas", Password: "azeaze"}
|
|
||||||
register(t, s, user)
|
|
||||||
|
|
||||||
err := s.Request("PUT", "/admin-panel/set-config", settingSet{"require-login", "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/all", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cookie := s.sessionCookie
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
err = s.Request("GET", "/all", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Should redirect to login if RequireLogin
|
|
||||||
err = s.Request("GET", "/"+gist1db.User.Username+"/"+gist1db.Uuid, nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = cookie
|
|
||||||
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{"allow-gists-without-login", "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
// Should return results
|
|
||||||
err = s.Request("GET", "/"+gist1db.User.Username+"/"+gist1db.Uuid, nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitOperations(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
admin := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, admin)
|
|
||||||
s.sessionCookie = ""
|
|
||||||
register(t, s, db.UserDTO{Username: "fujiwara", Password: "fujiwara"})
|
|
||||||
s.sessionCookie = ""
|
|
||||||
register(t, s, db.UserDTO{Username: "kaguya", Password: "kaguya"})
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "kaguya-pub-gist",
|
|
||||||
URL: "kaguya-pub-gist",
|
|
||||||
Description: "kaguya's first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PublicVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"kaguya-file.txt"},
|
|
||||||
Content: []string{
|
|
||||||
"yeah",
|
|
||||||
},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist2 := db.GistDTO{
|
|
||||||
Title: "kaguya-unl-gist",
|
|
||||||
URL: "kaguya-unl-gist",
|
|
||||||
Description: "kaguya's second gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.UnlistedVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"kaguya-file.txt"},
|
|
||||||
Content: []string{
|
|
||||||
"cool",
|
|
||||||
},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist3 := db.GistDTO{
|
|
||||||
Title: "kaguya-priv-gist",
|
|
||||||
URL: "kaguya-priv-gist",
|
|
||||||
Description: "kaguya's second gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.PrivateVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{"kaguya-file.txt"},
|
|
||||||
Content: []string{
|
|
||||||
"super",
|
|
||||||
},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist3, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
credentials string
|
|
||||||
user string
|
|
||||||
url string
|
|
||||||
pushOptions string
|
|
||||||
expectErrorClone bool
|
|
||||||
expectErrorCheck bool
|
|
||||||
expectErrorPush bool
|
|
||||||
}{
|
|
||||||
{":", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
|
||||||
{":", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
|
||||||
{":", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", "", false, false, false},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", "", false, false, false},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", "", false, false, false},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
login(t, s, admin)
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{"require-login", "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testsRequireLogin := []struct {
|
|
||||||
credentials string
|
|
||||||
user string
|
|
||||||
url string
|
|
||||||
pushOptions string
|
|
||||||
expectErrorClone bool
|
|
||||||
expectErrorCheck bool
|
|
||||||
expectErrorPush bool
|
|
||||||
}{
|
|
||||||
{":", "kaguya", "kaguya-pub-gist", "", true, true, true},
|
|
||||||
{":", "kaguya", "kaguya-unl-gist", "", true, true, true},
|
|
||||||
{":", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", "", false, false, false},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", "", false, false, false},
|
|
||||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", "", false, false, false},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testsRequireLogin {
|
|
||||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
login(t, s, admin)
|
|
||||||
err = s.Request("PUT", "/admin-panel/set-config", settingSet{"allow-gists-without-login", "1"}, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitInit(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
admin := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, admin)
|
|
||||||
s.sessionCookie = ""
|
|
||||||
register(t, s, db.UserDTO{Username: "fujiwara", Password: "fujiwara"})
|
|
||||||
s.sessionCookie = ""
|
|
||||||
register(t, s, db.UserDTO{Username: "kaguya", Password: "kaguya"})
|
|
||||||
|
|
||||||
testsNewWithPush := []struct {
|
|
||||||
credentials string
|
|
||||||
user string
|
|
||||||
url string
|
|
||||||
pushOptions string
|
|
||||||
expectErrorClone bool
|
|
||||||
expectErrorCheck bool
|
|
||||||
expectErrorPush bool
|
|
||||||
}{
|
|
||||||
{":", "kaguya", "gist1", "", true, true, true},
|
|
||||||
{"kaguya:wrongpass", "kaguya", "gist2", "", true, true, true},
|
|
||||||
{"fujiwara:fujiwara", "kaguya", "gist3", "", true, true, true},
|
|
||||||
{"kaguya:kaguya", "kaguya", "gist4", "", false, false, false},
|
|
||||||
{"kaguya:kaguya", "kaguya", "gist5/g", "", true, true, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testsNewWithPush {
|
|
||||||
gitInitPush(t, test.credentials, test.user, test.url, "newfile.txt", test.pushOptions, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "kaguya", gist1db.User.Username)
|
|
||||||
|
|
||||||
for _, test := range testsNewWithPush {
|
|
||||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "newfile.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := db.CountAll(db.Gist{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(1), count)
|
|
||||||
|
|
||||||
testsNewWithInit := []struct {
|
|
||||||
credentials string
|
|
||||||
url string
|
|
||||||
pushOptions string
|
|
||||||
expectErrorPush bool
|
|
||||||
}{
|
|
||||||
{":", "init", "", true},
|
|
||||||
{"fujiwara:wrongpass", "init", "", true},
|
|
||||||
{"kaguya:kaguya", "init", "", false},
|
|
||||||
{"fujiwara:fujiwara", "init", "", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testsNewWithInit {
|
|
||||||
gitInitPush(t, test.credentials, "kaguya", test.url, "newfile.txt", test.pushOptions, test.expectErrorPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err = db.CountAll(db.Gist{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(3), count)
|
|
||||||
|
|
||||||
gist2db, err := db.GetGistByID("2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "kaguya", gist2db.User.Username)
|
|
||||||
|
|
||||||
gist3db, err := db.GetGistByID("3")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "fujiwara", gist3db.User.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientGitClone(creds string, user string, url string) error {
|
|
||||||
return exec.Command("git", "clone", "http://"+creds+"@localhost:6157/"+user+"/"+url, filepath.Join(config.GetHomeDir(), "tmp", url)).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientGitPush(url string, pushOptions string, file string) error {
|
|
||||||
f, err := os.Create(filepath.Join(config.GetHomeDir(), "tmp", url, file))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = f.WriteString("new file")
|
|
||||||
_ = f.Close()
|
|
||||||
|
|
||||||
_ = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "add", file).Run()
|
|
||||||
_ = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "commit", "-m", "new file").Run()
|
|
||||||
if pushOptions != "" {
|
|
||||||
err = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "push", pushOptions, "origin").Run()
|
|
||||||
} else {
|
|
||||||
err = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "push", "origin").Run()
|
|
||||||
}
|
|
||||||
_ = os.RemoveAll(filepath.Join(config.GetHomeDir(), "tmp", url))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientGitInit(path string) error {
|
|
||||||
return exec.Command("git", "init", "--initial-branch=master", filepath.Join(config.GetHomeDir(), "tmp", path)).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientGitSetRemote(path string, remoteName string, remoteUrl string) error {
|
|
||||||
return exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", path), "remote", "add", remoteName, remoteUrl).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientCheckRepo(url string, file string) error {
|
|
||||||
_, err := os.ReadFile(filepath.Join(config.GetHomeDir(), "tmp", url, file))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func gitCloneCheckPush(t *testing.T, credentials, owner, url, filename, pushOptions string, expectErrorClone, expectErrorCheck, expectErrorPush bool) {
|
|
||||||
log.Debug().Msgf("Testing %s %s %t %t %t", credentials, url, expectErrorClone, expectErrorCheck, expectErrorPush)
|
|
||||||
err := clientGitClone(credentials, owner, url)
|
|
||||||
if expectErrorClone {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
err = clientCheckRepo(url, filename)
|
|
||||||
if expectErrorCheck {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
err = clientGitPush(url, pushOptions, filename)
|
|
||||||
if expectErrorPush {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gitInitPush(t *testing.T, credentials, owner, url, filename, pushOptions string, expectErrorPush bool) {
|
|
||||||
log.Debug().Msgf("Testing %s %s %t", credentials, url, expectErrorPush)
|
|
||||||
err := clientGitInit(url)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if url == "init" {
|
|
||||||
err = clientGitSetRemote(url, "origin", "http://"+credentials+"@localhost:6157/init/")
|
|
||||||
} else {
|
|
||||||
err = clientGitSetRemote(url, "origin", "http://"+credentials+"@localhost:6157/"+owner+"/"+url)
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = clientGitPush(url, pushOptions, filename)
|
|
||||||
if expectErrorPush {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGists(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/all", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/", nil, 400)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, uint(1), gist1db.ID)
|
|
||||||
require.Equal(t, gist1.Title, gist1db.Title)
|
|
||||||
require.Equal(t, gist1.Description, gist1db.Description)
|
|
||||||
require.Regexp(t, "[a-f0-9]{32}", gist1db.Uuid)
|
|
||||||
require.Equal(t, user1.Username, gist1db.User.Username)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/"+gist1db.User.Username+"/"+gist1db.Uuid, nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1files, err := git.GetFilesOfRepository(gist1db.User.Username, gist1db.Uuid, "HEAD")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 3, len(gist1files))
|
|
||||||
|
|
||||||
gist1fileContent, _, err := git.GetFileContent(gist1db.User.Username, gist1db.Uuid, "HEAD", gist1.Name[0], false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, gist1.Content[0], gist1fileContent)
|
|
||||||
|
|
||||||
gist2 := db.GistDTO{
|
|
||||||
Title: "gist2",
|
|
||||||
Description: "my second gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist3 := db.GistDTO{
|
|
||||||
Title: "gist3",
|
|
||||||
Description: "my third gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{""},
|
|
||||||
Content: []string{"yeah"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist3, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist3db, err := db.GetGistByID("3")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist3files, err := git.GetFilesOfRepository(gist3db.User.Username, gist3db.Uuid, "HEAD")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "gistfile1.txt", gist3files[0])
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/edit", nil, 400)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1.Name = []string{"gist1.txt"}
|
|
||||||
gist1.Content = []string{"only want one gist"}
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/edit", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1files, err = git.GetFilesOfRepository(gist1db.User.Username, gist1db.Uuid, "HEAD")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(gist1files))
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/delete", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVisibility(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: db.UnlistedVisibility,
|
|
||||||
},
|
|
||||||
Name: []string{""},
|
|
||||||
Content: []string{"yeah"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, db.UnlistedVisibility, gist1db.Private)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.PrivateVisibility}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist1db, err = db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, db.PrivateVisibility, gist1db.Private)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.PublicVisibility}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist1db, err = db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, db.PublicVisibility, gist1db.Private)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.UnlistedVisibility}, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist1db, err = db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, db.UnlistedVisibility, gist1db.Private)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLikeFork(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 1,
|
|
||||||
},
|
|
||||||
Name: []string{""},
|
|
||||||
Content: []string{"yeah"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.sessionCookie = ""
|
|
||||||
|
|
||||||
user2 := db.UserDTO{Username: "kaguya", Password: "kaguya"}
|
|
||||||
register(t, s, user2)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 0, gist1db.NbLikes)
|
|
||||||
likeCount, err := db.CountAll(db.Like{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(0), likeCount)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/like", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist1db, err = db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, gist1db.NbLikes)
|
|
||||||
likeCount, err = db.CountAll(db.Like{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(1), likeCount)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/like", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist1db, err = db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 0, gist1db.NbLikes)
|
|
||||||
likeCount, err = db.CountAll(db.Like{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int64(0), likeCount)
|
|
||||||
|
|
||||||
err = s.Request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/fork", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gist2db, err := db.GetGistByID("2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, gist1db.Title, gist2db.Title)
|
|
||||||
require.Equal(t, gist1db.Description, gist2db.Description)
|
|
||||||
require.Equal(t, gist1db.Private, gist2db.Private)
|
|
||||||
require.Equal(t, user2.Username, gist2db.User.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCustomUrl(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
URL: "my-gist",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, uint(1), gist1db.ID)
|
|
||||||
require.Equal(t, gist1.Title, gist1db.Title)
|
|
||||||
require.Equal(t, gist1.Description, gist1db.Description)
|
|
||||||
require.Regexp(t, "[a-f0-9]{32}", gist1db.Uuid)
|
|
||||||
require.Equal(t, gist1.URL, gist1db.URL)
|
|
||||||
require.Equal(t, user1.Username, gist1db.User.Username)
|
|
||||||
|
|
||||||
gist1dbUuid, err := db.GetGist(user1.Username, gist1db.Uuid)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, gist1db, gist1dbUuid)
|
|
||||||
|
|
||||||
gist1dbUrl, err := db.GetGist(user1.Username, gist1.URL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, gist1db, gist1dbUrl)
|
|
||||||
|
|
||||||
require.Equal(t, gist1.URL, gist1db.Identifier())
|
|
||||||
|
|
||||||
gist2 := db.GistDTO{
|
|
||||||
Title: "gist2",
|
|
||||||
Description: "my second gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist2db, err := db.GetGistByID("2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, gist2db.Uuid, gist2db.Identifier())
|
|
||||||
require.NotEqual(t, gist2db.URL, gist2db.Identifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTopics(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
|
|
||||||
gist1 := db.GistDTO{
|
|
||||||
Title: "gist1",
|
|
||||||
URL: "my-gist",
|
|
||||||
Description: "my first gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "topic1 topic2 topic3",
|
|
||||||
}
|
|
||||||
err := s.Request("POST", "/", gist1, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist1db, err := db.GetGistByID("1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, []db.GistTopic{
|
|
||||||
{GistID: 1, Topic: "topic1"},
|
|
||||||
{GistID: 1, Topic: "topic2"},
|
|
||||||
{GistID: 1, Topic: "topic3"},
|
|
||||||
}, gist1db.Topics)
|
|
||||||
|
|
||||||
gist2 := db.GistDTO{
|
|
||||||
Title: "gist2",
|
|
||||||
URL: "my-gist",
|
|
||||||
Description: "my second gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "topic1 topic2 topic3 topic2 topic4 topic1",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist2, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist2db, err := db.GetGistByID("2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []db.GistTopic{
|
|
||||||
{GistID: 2, Topic: "topic1"},
|
|
||||||
{GistID: 2, Topic: "topic2"},
|
|
||||||
{GistID: 2, Topic: "topic3"},
|
|
||||||
{GistID: 2, Topic: "topic4"},
|
|
||||||
}, gist2db.Topics)
|
|
||||||
|
|
||||||
gist3 := db.GistDTO{
|
|
||||||
Title: "gist3",
|
|
||||||
URL: "my-gist",
|
|
||||||
Description: "my third gist",
|
|
||||||
VisibilityDTO: db.VisibilityDTO{
|
|
||||||
Private: 0,
|
|
||||||
},
|
|
||||||
Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"},
|
|
||||||
Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"},
|
|
||||||
Topics: "topic1 topic2 topic3 topic4 topic5 topic6 topic7 topic8 topic9 topic10 topic11",
|
|
||||||
}
|
|
||||||
err = s.Request("POST", "/", gist3, 400)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gist3.Topics = "topictoolongggggggggggggggggggggggggggggggggggggggg"
|
|
||||||
err = s.Request("POST", "/", gist3, 400)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -10,80 +8,74 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/gorilla/schema"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"github.com/thomiceli/opengist/internal/index"
|
||||||
|
"github.com/thomiceli/opengist/internal/web/context"
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
||||||
"github.com/thomiceli/opengist/internal/web/server"
|
"github.com/thomiceli/opengist/internal/web/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
var databaseType string
|
var databaseType string
|
||||||
|
var formEncoder *schema.Encoder
|
||||||
|
|
||||||
type TestServer struct {
|
func init() {
|
||||||
|
formEncoder = schema.NewEncoder()
|
||||||
|
formEncoder.SetAliasTag("form")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
server *server.Server
|
server *server.Server
|
||||||
sessionCookie string
|
SessionCookie string
|
||||||
|
contextData echo.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestServer() (*TestServer, error) {
|
func (s *Server) Request(t *testing.T, method, uri string, data interface{}, expectedCode int) *http.Response {
|
||||||
s := &TestServer{
|
return s.RequestWithHeaders(t, method, uri, data, expectedCode, nil)
|
||||||
server: server.NewServer(true, filepath.Join(config.GetHomeDir(), "tmp", "sessions"), true),
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.start()
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestServer) start() {
|
func (s *Server) RequestWithHeaders(t *testing.T, method, uri string, data interface{}, expectedCode int, headers map[string]string) *http.Response {
|
||||||
s.server.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) stop() {
|
|
||||||
s.server.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) Request(method, uri string, data interface{}, expectedCode int, responsePtr ...*http.Response) error {
|
|
||||||
return s.RequestWithHeaders(method, uri, data, expectedCode, nil, responsePtr...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) RequestWithHeaders(method, uri string, data interface{}, expectedCode int, headers map[string]string, responsePtr ...*http.Response) error {
|
|
||||||
var bodyReader io.Reader
|
var bodyReader io.Reader
|
||||||
if method == http.MethodPost || method == http.MethodPut {
|
if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete {
|
||||||
values := structToURLValues(data)
|
if values, ok := data.(url.Values); ok {
|
||||||
bodyReader = strings.NewReader(values.Encode())
|
bodyReader = strings.NewReader(values.Encode())
|
||||||
|
} else if data != nil {
|
||||||
|
values := url.Values{}
|
||||||
|
_ = formEncoder.Encode(data, values)
|
||||||
|
bodyReader = strings.NewReader(values.Encode())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(method, "http://localhost:6157"+uri, bodyReader)
|
req := httptest.NewRequest(method, uri, bodyReader)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
if method == http.MethodPost || method == http.MethodPut {
|
if method == http.MethodPost || method == http.MethodPut {
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Sec-Fetch-Site", "same-origin")
|
||||||
|
|
||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
req.Header.Set(key, value)
|
req.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.sessionCookie != "" {
|
if s.SessionCookie != "" {
|
||||||
req.AddCookie(&http.Cookie{Name: "session", Value: s.sessionCookie})
|
req.AddCookie(&http.Cookie{Name: "session", Value: s.SessionCookie})
|
||||||
}
|
}
|
||||||
|
|
||||||
s.server.ServeHTTP(w, req)
|
s.server.ServeHTTP(w, req)
|
||||||
|
if expectedCode != 0 {
|
||||||
if w.Code != expectedCode {
|
require.Equalf(t, expectedCode, w.Code, "Unexpected status code for %s %s: got %d, expected %d", method, uri, w.Code, expectedCode)
|
||||||
return fmt.Errorf("unexpected status code %d, expected %d", w.Code, expectedCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == http.MethodPost {
|
if method == http.MethodPost {
|
||||||
if strings.Contains(uri, "/login") || strings.Contains(uri, "/register") {
|
if strings.Contains(uri, "/login") {
|
||||||
cookie := ""
|
cookie := ""
|
||||||
h := w.Header().Get("Set-Cookie")
|
h := w.Header().Get("Set-Cookie")
|
||||||
parts := strings.Split(h, "; ")
|
parts := strings.Split(h, "; ")
|
||||||
@@ -93,91 +85,127 @@ func (s *TestServer) RequestWithHeaders(method, uri string, data interface{}, ex
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cookie == "" {
|
s.SessionCookie = strings.TrimPrefix(cookie, "session=")
|
||||||
return errors.New("unable to find access session token in response headers")
|
|
||||||
}
|
|
||||||
s.sessionCookie = strings.TrimPrefix(cookie, "session=")
|
|
||||||
} else if strings.Contains(uri, "/logout") {
|
} else if strings.Contains(uri, "/logout") {
|
||||||
s.sessionCookie = ""
|
s.SessionCookie = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a response pointer was provided, fill it with the response data
|
return w.Result()
|
||||||
if len(responsePtr) > 0 && responsePtr[0] != nil {
|
}
|
||||||
*responsePtr[0] = *w.Result()
|
|
||||||
|
func (s *Server) RawRequest(t *testing.T, req *http.Request, expectedCode int) *http.Response {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
req.Header.Set("Sec-Fetch-Site", "same-origin")
|
||||||
|
|
||||||
|
if s.SessionCookie != "" {
|
||||||
|
req.AddCookie(&http.Cookie{Name: "session", Value: s.SessionCookie})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.server.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, expectedCode, w.Code, "unexpected status code for %s %s", req.Method, req.URL.Path)
|
||||||
|
|
||||||
|
return w.Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) StartHttpServer(t *testing.T) string {
|
||||||
|
hs := httptest.NewServer(s.server)
|
||||||
|
t.Cleanup(hs.Close)
|
||||||
|
return hs.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) User() *db.User {
|
||||||
|
s.Request(nil, "GET", "/", nil, 0)
|
||||||
|
if user, ok := s.contextData["userLogged"].(*db.User); ok {
|
||||||
|
return user
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func structToURLValues(s interface{}) url.Values {
|
func (s *Server) TestCtxData(t *testing.T, expected echo.Map) {
|
||||||
v := url.Values{}
|
for key, expectedValue := range expected {
|
||||||
if s == nil {
|
actualValue, exists := s.contextData[key]
|
||||||
return v
|
require.True(t, exists, "Key %q not found in context data", key)
|
||||||
|
require.Equal(t, expectedValue, actualValue, "Context data mismatch for key %q", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
rValue := reflect.ValueOf(s)
|
|
||||||
if rValue.Kind() != reflect.Struct {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < rValue.NumField(); i++ {
|
|
||||||
field := rValue.Type().Field(i)
|
|
||||||
tag := field.Tag.Get("form")
|
|
||||||
if tag != "" || field.Anonymous {
|
|
||||||
if field.Type.Kind() == reflect.Int {
|
|
||||||
fieldValue := rValue.Field(i).Int()
|
|
||||||
v.Add(tag, strconv.FormatInt(fieldValue, 10))
|
|
||||||
} else if field.Type.Kind() == reflect.Uint {
|
|
||||||
fieldValue := rValue.Field(i).Uint()
|
|
||||||
v.Add(tag, strconv.FormatUint(fieldValue, 10))
|
|
||||||
} else if field.Type.Kind() == reflect.Slice {
|
|
||||||
fieldValue := rValue.Field(i).Interface().([]string)
|
|
||||||
for _, va := range fieldValue {
|
|
||||||
v.Add(tag, va)
|
|
||||||
}
|
|
||||||
} else if field.Type.Kind() == reflect.Struct {
|
|
||||||
for key, val := range structToURLValues(rValue.Field(i).Interface()) {
|
|
||||||
for _, vv := range val {
|
|
||||||
v.Add(key, vv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fieldValue := rValue.Field(i).String()
|
|
||||||
v.Add(tag, fieldValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup(t *testing.T) *TestServer {
|
func (s *Server) Register(t *testing.T, user string) {
|
||||||
_ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1")
|
s.Request(t, "POST", "/register", db.UserDTO{Username: user, Password: user}, 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Login(t *testing.T, user string) {
|
||||||
|
s.Request(t, "POST", "/login", db.UserDTO{Username: user, Password: user}, 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Logout() {
|
||||||
|
s.SessionCookie = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) CreateGist(t *testing.T, visibility string) (gistPath string, gist *db.Gist, username, identifier string) {
|
||||||
|
s.Request(t, "POST", "/register", db.UserDTO{Username: "thomas", Password: "thomas"}, 0)
|
||||||
|
s.Login(t, "thomas")
|
||||||
|
|
||||||
|
resp := s.Request(t, "POST", "/", url.Values{
|
||||||
|
"title": {"Test"},
|
||||||
|
"name": {"file.txt", "otherfile.txt"},
|
||||||
|
"content": {"hello world", "other content"},
|
||||||
|
"topics": {"hello opengist"},
|
||||||
|
"private": {visibility},
|
||||||
|
}, 302)
|
||||||
|
|
||||||
|
// Extract gist identifier from redirect
|
||||||
|
location := resp.Header.Get("Location")
|
||||||
|
parts := strings.Split(strings.TrimPrefix(location, "/"), "/")
|
||||||
|
require.Len(t, parts, 2, "Expected redirect format: /{username}/{identifier}")
|
||||||
|
|
||||||
|
gistUsername := parts[0]
|
||||||
|
gistIdentifier := parts[1]
|
||||||
|
|
||||||
|
gist, err := db.GetGist(gistUsername, gistIdentifier)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, gist)
|
||||||
|
|
||||||
|
gistPath = filepath.Join(config.GetHomeDir(), git.ReposDirectory, "thomas", gist.Uuid)
|
||||||
|
|
||||||
|
// Verify gist exists on filesystem
|
||||||
|
_, err = os.Stat(gistPath)
|
||||||
|
require.NoError(t, err, "Gist repository should exist at %s", gistPath)
|
||||||
|
|
||||||
|
username = gist.User.Username
|
||||||
|
identifier = gist.Identifier()
|
||||||
|
|
||||||
|
s.Logout()
|
||||||
|
return gistPath, gist, username, identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(t *testing.T) *Server {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1")
|
||||||
|
|
||||||
err := config.InitConfig("", io.Discard)
|
err := config.InitConfig("", io.Discard)
|
||||||
require.NoError(t, err, "Could not init config")
|
require.NoError(t, err, "Could not init config")
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755)
|
config.C.LogLevel = "warn"
|
||||||
require.NoError(t, err, "Could not create Opengist home directory")
|
config.C.LogOutput = "stdout"
|
||||||
|
config.C.GitDefaultBranch = "master"
|
||||||
|
config.C.OpengistHome = tmpDir
|
||||||
|
|
||||||
config.SetupSecretKey()
|
config.SetupSecretKey()
|
||||||
|
|
||||||
git.ReposDirectory = filepath.Join("tests")
|
|
||||||
|
|
||||||
config.C.Index = ""
|
|
||||||
config.C.LogLevel = "error"
|
|
||||||
config.C.GitDefaultBranch = "master"
|
|
||||||
config.InitLog()
|
config.InitLog()
|
||||||
|
|
||||||
|
tmpGitConfig := filepath.Join(tmpDir, "gitconfig")
|
||||||
|
t.Setenv("GIT_CONFIG_GLOBAL", tmpGitConfig)
|
||||||
|
|
||||||
err = exec.Command("git", "config", "--global", "--type", "bool", "push.autoSetupRemote", "true").Run()
|
err = exec.Command("git", "config", "--global", "--type", "bool", "push.autoSetupRemote", "true").Run()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = exec.Command("git", "config", "--global", "user.email", "test@opengist.io").Run()
|
err = exec.Command("git", "config", "--global", "user.email", "test@opengist.io").Run()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = exec.Command("git", "config", "--global", "user.name", "test").Run()
|
err = exec.Command("git", "config", "--global", "user.name", "test").Run()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
homePath := config.GetHomeDir()
|
homePath := config.GetHomeDir()
|
||||||
log.Info().Msg("Data directory: " + homePath)
|
|
||||||
|
|
||||||
var databaseDsn string
|
var databaseDsn string
|
||||||
databaseType = os.Getenv("OPENGIST_TEST_DB")
|
databaseType = os.Getenv("OPENGIST_TEST_DB")
|
||||||
@@ -187,70 +215,51 @@ func Setup(t *testing.T) *TestServer {
|
|||||||
case "mysql":
|
case "mysql":
|
||||||
databaseDsn = "mysql://root:opengist@localhost:3306/opengist_test"
|
databaseDsn = "mysql://root:opengist@localhost:3306/opengist_test"
|
||||||
default:
|
default:
|
||||||
databaseDsn = "file:" + filepath.Join(homePath, "tmp", "opengist_test.db")
|
databaseDsn = config.C.DBUri
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Join(homePath, "tests"), 0755)
|
err = os.MkdirAll(filepath.Join(homePath, "sessions"), 0755)
|
||||||
require.NoError(t, err, "Could not create tests directory")
|
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Join(homePath, "tmp", "sessions"), 0755)
|
|
||||||
require.NoError(t, err, "Could not create sessions directory")
|
require.NoError(t, err, "Could not create sessions directory")
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(homePath, "repos"), 0755)
|
||||||
|
require.NoError(t, err, "Could not create repos directory")
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Join(homePath, "tmp", "repos"), 0755)
|
err = os.MkdirAll(filepath.Join(homePath, "tmp", "repos"), 0755)
|
||||||
require.NoError(t, err, "Could not create tmp repos directory")
|
require.NoError(t, err, "Could not create tmp repos directory")
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(homePath, "custom"), 0755)
|
||||||
|
require.NoError(t, err, "Could not create custom directory")
|
||||||
|
|
||||||
err = db.Setup(databaseDsn)
|
err = db.Setup(databaseDsn)
|
||||||
require.NoError(t, err, "Could not initialize database")
|
require.NoError(t, err, "Could not initialize database")
|
||||||
|
|
||||||
if err != nil {
|
if index.IndexEnabled() {
|
||||||
log.Fatal().Err(err).Msg("Could not initialize database")
|
go index.NewIndexer(index.IndexType())
|
||||||
}
|
}
|
||||||
|
|
||||||
// err = index.Open(filepath.Join(homePath, "testsindex", "opengist.index"))
|
s := &Server{
|
||||||
// require.NoError(t, err, "Could not open index")
|
server: server.NewServer(true),
|
||||||
|
}
|
||||||
|
|
||||||
s, err := newTestServer()
|
s.server.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
require.NoError(t, err, "Failed to create test server")
|
return func(c echo.Context) error {
|
||||||
|
err := next(c)
|
||||||
|
if data, ok := c.Request().Context().Value(context.DataKeyStr).(echo.Map); ok {
|
||||||
|
s.contextData = data
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func Teardown(t *testing.T, s *TestServer) {
|
func Teardown(t *testing.T) {
|
||||||
s.stop()
|
switch databaseType {
|
||||||
|
case "postgres", "mysql":
|
||||||
//err := db.Close()
|
err := db.TruncateDatabase()
|
||||||
//require.NoError(t, err, "Could not close database")
|
require.NoError(t, err, "Could not truncate database")
|
||||||
|
|
||||||
err := db.TruncateDatabase()
|
|
||||||
require.NoError(t, err, "Could not truncate database")
|
|
||||||
|
|
||||||
err = os.RemoveAll(filepath.Join(config.GetHomeDir(), "tests"))
|
|
||||||
require.NoError(t, err, "Could not remove repos directory")
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
err = db.Close()
|
|
||||||
require.NoError(t, err, "Could not close database")
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
}
|
||||||
err = os.RemoveAll(filepath.Join(config.GetHomeDir(), "tmp"))
|
|
||||||
require.NoError(t, err, "Could not remove tmp directory")
|
|
||||||
|
|
||||||
// err = os.RemoveAll(path.Join(config.C.OpengistHome, "testsindex"))
|
|
||||||
// require.NoError(t, err, "Could not remove repos directory")
|
|
||||||
|
|
||||||
// err = index.Close()
|
|
||||||
// require.NoError(t, err, "Could not close index")
|
|
||||||
}
|
|
||||||
|
|
||||||
type settingSet struct {
|
|
||||||
key string `form:"key"`
|
|
||||||
value string `form:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type invitationAdmin struct {
|
|
||||||
nbMax string `form:"nbMax"`
|
|
||||||
expiredAtUnix string `form:"expiredAtUnix"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestMetricsServer() *metrics.Server {
|
func NewTestMetricsServer() *metrics.Server {
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettingsPage(t *testing.T) {
|
|
||||||
s := Setup(t)
|
|
||||||
defer Teardown(t, s)
|
|
||||||
|
|
||||||
err := s.Request("GET", "/settings", nil, 302)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
user1 := db.UserDTO{Username: "thomas", Password: "thomas"}
|
|
||||||
register(t, s, user1)
|
|
||||||
login(t, s, user1)
|
|
||||||
|
|
||||||
err = s.Request("GET", "/settings", nil, 200)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
277
test.md
Normal file
277
test.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
description: Testing handler and middleware
|
||||||
|
slug: /testing
|
||||||
|
sidebar_position: 13
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
## Testing Handler
|
||||||
|
|
||||||
|
`GET` `/users/:id`
|
||||||
|
|
||||||
|
Handler below retrieves user by id from the database. If user is not found it returns
|
||||||
|
`404` error with a message.
|
||||||
|
|
||||||
|
### CreateUser
|
||||||
|
|
||||||
|
`POST` `/users`
|
||||||
|
|
||||||
|
- Accepts JSON payload
|
||||||
|
- On success `201 - Created`
|
||||||
|
- On error `500 - Internal Server Error`
|
||||||
|
|
||||||
|
### GetUser
|
||||||
|
|
||||||
|
`GET` `/users/:email`
|
||||||
|
|
||||||
|
- On success `200 - OK`
|
||||||
|
- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
|
||||||
|
|
||||||
|
`handler.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
User struct {
|
||||||
|
Name string `json:"name" form:"name"`
|
||||||
|
Email string `json:"email" form:"email"`
|
||||||
|
}
|
||||||
|
handler struct {
|
||||||
|
db map[string]*User
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) createUser(c *echo.Context) error {
|
||||||
|
u := new(User)
|
||||||
|
if err := c.Bind(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusCreated, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getUser(c *echo.Context) error {
|
||||||
|
email := c.Param("email")
|
||||||
|
user := h.db[email]
|
||||||
|
if user == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "user not found")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`handler_test.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"github.com/labstack/echo/v5/echotest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mockDB = map[string]*User{
|
||||||
|
"jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
|
||||||
|
}
|
||||||
|
userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
// Setup
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
|
||||||
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
h := &controller{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.createUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
assert.Equal(t, userJSON, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same test as above but using `echotest` package helpers
|
||||||
|
func TestCreateUserWithEchoTest(t *testing.T) {
|
||||||
|
c, rec := echotest.ContextConfig{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||||
|
},
|
||||||
|
JSONBody: []byte(`{"name":"Jon Snow","email":"jon@labstack.com"}`),
|
||||||
|
}.ToContextRecorder(t)
|
||||||
|
|
||||||
|
h := &controller{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.createUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same test as above but even shorter
|
||||||
|
func TestCreateUserWithEchoTest2(t *testing.T) {
|
||||||
|
h := &controller{mockDB}
|
||||||
|
|
||||||
|
rec := echotest.ContextConfig{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||||
|
},
|
||||||
|
JSONBody: []byte(`{"name":"Jon Snow","email":"jon@labstack.com"}`),
|
||||||
|
}.ServeWithHandler(t, h.createUser)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUser(t *testing.T) {
|
||||||
|
// Setup
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
c.SetPath("/users/:email")
|
||||||
|
c.SetPathValues(echo.PathValues{
|
||||||
|
{Name: "email", Value: "jon@labstack.com"},
|
||||||
|
})
|
||||||
|
h := &controller{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.getUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, userJSON, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserWithEchoTest(t *testing.T) {
|
||||||
|
c, rec := echotest.ContextConfig{
|
||||||
|
PathValues: echo.PathValues{
|
||||||
|
{Name: "email", Value: "jon@labstack.com"},
|
||||||
|
},
|
||||||
|
Headers: map[string][]string{
|
||||||
|
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||||
|
},
|
||||||
|
JSONBody: []byte(userJSON),
|
||||||
|
}.ToContextRecorder(t)
|
||||||
|
|
||||||
|
h := &controller{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.getUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Form Payload
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import "net/url"
|
||||||
|
f := make(url.Values)
|
||||||
|
f.Set("name", "Jon Snow")
|
||||||
|
f.Set("email", "jon@labstack.com")
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
|
||||||
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||||
|
```
|
||||||
|
|
||||||
|
Multipart form payload:
|
||||||
|
```go
|
||||||
|
func TestContext_MultipartForm(t *testing.T) {
|
||||||
|
testConf := echotest.ContextConfig{
|
||||||
|
MultipartForm: &echotest.MultipartForm{
|
||||||
|
Fields: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
Files: []echotest.MultipartFormFile{
|
||||||
|
{
|
||||||
|
Fieldname: "file",
|
||||||
|
Filename: "test.json",
|
||||||
|
Content: echotest.LoadBytes(t, "testdata/test.json"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := testConf.ToContext(t)
|
||||||
|
|
||||||
|
assert.Equal(t, "value", c.FormValue("key"))
|
||||||
|
assert.Equal(t, http.MethodPost, c.Request().Method)
|
||||||
|
assert.Equal(t, true, strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), "multipart/form-data; boundary="))
|
||||||
|
|
||||||
|
fv, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "test.json", fv.Filename)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Path Params
|
||||||
|
|
||||||
|
```go
|
||||||
|
c.SetPathValues(echo.PathValues{
|
||||||
|
{Name: "id", Value: "1"},
|
||||||
|
{Name: "email", Value: "jon@labstack.com"},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Query Params
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import "net/url"
|
||||||
|
q := make(url.Values)
|
||||||
|
q.Set("email", "jon@labstack.com")
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Middleware
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestCreateUserWithEchoTest2(t *testing.T) {
|
||||||
|
handler := func(c *echo.Context) error {
|
||||||
|
return c.JSON(http.StatusTeapot, fmt.Sprintf("email: %s", c.Param("email")))
|
||||||
|
}
|
||||||
|
middleware := func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c *echo.Context) error {
|
||||||
|
c.Set("user_id", int64(1234))
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c, rec := echotest.ContextConfig{
|
||||||
|
PathValues: echo.PathValues{{Name: "email", Value: "jon@labstack.com"}},
|
||||||
|
}.ToContextRecorder(t)
|
||||||
|
|
||||||
|
err := middleware(handler)(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// check that middleware set the value
|
||||||
|
userID, err := echo.ContextGet[int64](c, "user_id")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1234), userID)
|
||||||
|
|
||||||
|
// check that handler returned the correct response
|
||||||
|
assert.Equal(t, http.StatusTeapot, rec.Code)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For now you can look into built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
|
||||||
158
test2.md
Normal file
158
test2.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
description: Testing handler and middleware
|
||||||
|
slug: /testing
|
||||||
|
sidebar_position: 13
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
## Testing Handler
|
||||||
|
|
||||||
|
`GET` `/users/:id`
|
||||||
|
|
||||||
|
Handler below retrieves user by id from the database. If user is not found it returns
|
||||||
|
`404` error with a message.
|
||||||
|
|
||||||
|
### CreateUser
|
||||||
|
|
||||||
|
`POST` `/users`
|
||||||
|
|
||||||
|
- Accepts JSON payload
|
||||||
|
- On success `201 - Created`
|
||||||
|
- On error `500 - Internal Server Error`
|
||||||
|
|
||||||
|
### GetUser
|
||||||
|
|
||||||
|
`GET` `/users/:email`
|
||||||
|
|
||||||
|
- On success `200 - OK`
|
||||||
|
- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
|
||||||
|
|
||||||
|
`handler.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
User struct {
|
||||||
|
Name string `json:"name" form:"name"`
|
||||||
|
Email string `json:"email" form:"email"`
|
||||||
|
}
|
||||||
|
handler struct {
|
||||||
|
db map[string]*User
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) createUser(c echo.Context) error {
|
||||||
|
u := new(User)
|
||||||
|
if err := c.Bind(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusCreated, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getUser(c echo.Context) error {
|
||||||
|
email := c.Param("email")
|
||||||
|
user := h.db[email]
|
||||||
|
if user == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "user not found")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`handler_test.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mockDB = map[string]*User{
|
||||||
|
"jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
|
||||||
|
}
|
||||||
|
userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
// Setup
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
|
||||||
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
h := &handler{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.createUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||||
|
assert.Equal(t, userJSON, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUser(t *testing.T) {
|
||||||
|
// Setup
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
c.SetPath("/users/:email")
|
||||||
|
c.SetParamNames("email")
|
||||||
|
c.SetParamValues("jon@labstack.com")
|
||||||
|
h := &handler{mockDB}
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if assert.NoError(t, h.getUser(c)) {
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, userJSON, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Form Payload
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import "net/url"
|
||||||
|
f := make(url.Values)
|
||||||
|
f.Set("name", "Jon Snow")
|
||||||
|
f.Set("email", "jon@labstack.com")
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
|
||||||
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Path Params
|
||||||
|
|
||||||
|
```go
|
||||||
|
c.SetParamNames("id", "email")
|
||||||
|
c.SetParamValues("1", "jon@labstack.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Query Params
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import "net/url"
|
||||||
|
q := make(url.Values)
|
||||||
|
q.Set("email", "jon@labstack.com")
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Middleware
|
||||||
|
|
||||||
|
*TBD*
|
||||||
|
|
||||||
|
For now you can look into built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم