520 lines
14 KiB
Go
520 lines
14 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|