Improve test suite (#628)
Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>
This commit is contained in:
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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/render"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
"github.com/thomiceli/opengist/internal/web/handlers"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user