Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ checksum:
algorithm: sha256

release:
name_template: "phpMyAdmin Updater {{ .Tag }}"
github:
owner: jsas4coding
name: pma-up
151 changes: 77 additions & 74 deletions internal/downloader/downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)

func TestDownloadPhpMyAdmin(t *testing.T) {
func TestDownloadPhpMyAdmin_Success(t *testing.T) {
mockZipContent := []byte("PK\x03\x04 dummy zip content")

// Setup mock HTTP server to simulate phpMyAdmin download endpoint
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/zip")
if _, err := w.Write(mockZipContent); err != nil {
Expand All @@ -22,97 +22,100 @@

tempDir := t.TempDir()
version := "5.2.2"

mockDownloadURL := fmt.Sprintf("%s/phpMyAdmin-%s-all-languages.zip", server.URL, version)

// Execute the download function
downloadedFilePath, err := DownloadPhpMyAdmin(mockDownloadURL, tempDir, version)
filePath, err := DownloadPhpMyAdmin(mockDownloadURL, tempDir, version)
if err != nil {
t.Fatalf("expected no error, got %v", err)
t.Fatalf("unexpected error: %v", err)
}

// Verify the file exists
if _, statErr := os.Stat(downloadedFilePath); statErr != nil {
t.Fatalf("expected file to exist, but got error: %v", statErr)
if _, err := os.Stat(filePath); err != nil {
t.Fatalf("expected file, got stat error: %v", err)
}

// Verify the file content matches mock data
data, readErr := os.ReadFile(downloadedFilePath)
if readErr != nil {
t.Fatalf("failed to read downloaded file: %v", readErr)
data, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("failed to read file: %v", err)
}

if string(data) != string(mockZipContent) {
t.Errorf("downloaded file content does not match expected mock content")
t.Errorf("file content mismatch")
}
}

func TestDownloadPhpMyAdmin_FailureScenarios(t *testing.T) {
func TestDownloadPhpMyAdmin_InputValidation(t *testing.T) {
tests := []struct {
name string
serverFunc func() *httptest.Server
setupDir func() (string, error)
expectErr bool
name string
url string
dest string
ver string
experror string
}{
{
name: "http 500 error",
serverFunc: func() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "internal server error", http.StatusInternalServerError)
}))
},
setupDir: func() (string, error) {
return t.TempDir(), nil
},
expectErr: true,
},
{
name: "directory not writable",
serverFunc: func() *httptest.Server {
content := []byte("PK\x03\x04 dummy zip content")
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/zip")
if _, err := w.Write(content); err != nil {
t.Fatalf("failed to write mock zip content: %v", err)
}
}))
},
setupDir: func() (string, error) {
dir := t.TempDir()
if chmodErr := os.Chmod(dir, 0500); chmodErr != nil {
return "", chmodErr
}
return dir, nil
},
expectErr: true,
},
{"empty url", "", "/tmp", "5.2.2", "empty download URL"},
{"empty dest", "http://example.com/file.zip", "", "5.2.2", "empty destination directory"},
{"empty version", "http://example.com/file.zip", "/tmp", "", "empty version string"},
}

for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
server := tt.serverFunc()
defer server.Close()

tempDir, dirErr := tt.setupDir()
if dirErr != nil {
t.Fatalf("failed to setup dir: %v", dirErr)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := DownloadPhpMyAdmin(tc.url, tc.dest, tc.ver)
if err == nil || !strings.Contains(err.Error(), tc.experror) {
t.Errorf("expected error '%s', got '%v'", tc.experror, err)
}
defer func() {
// restore permission so tempdir can be cleaned up
_ = os.Chmod(tempDir, 0700)
}()
})
}
}

version := "5.2.2"
mockDownloadURL := fmt.Sprintf("%s/phpMyAdmin-%s-all-languages.zip", server.URL, version)
func TestDownloadPhpMyAdmin_RequestCreationFailure(t *testing.T) {
tempDir := t.TempDir()
_, err := DownloadPhpMyAdmin(":/invalid-url", tempDir, "5.2.2")
if err == nil || !strings.Contains(err.Error(), "failed to create HTTP request") {
t.Errorf("expected HTTP request creation error, got %v", err)
}
}

_, downloadErr := DownloadPhpMyAdmin(mockDownloadURL, tempDir, version)
if tt.expectErr && downloadErr == nil {
t.Errorf("expected error but got none")
}
if !tt.expectErr && downloadErr != nil {
t.Errorf("unexpected error: %v", downloadErr)
}
})
func TestDownloadPhpMyAdmin_ClientFailure(t *testing.T) {
tempDir := t.TempDir()
_, err := DownloadPhpMyAdmin("http://nonexistent.invalid/file.zip", tempDir, "5.2.2")
if err == nil || !strings.Contains(err.Error(), "failed to perform HTTP request") {
t.Errorf("expected client failure, got %v", err)
}
}

func TestDownloadPhpMyAdmin_ServerReturns500(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "internal error", http.StatusInternalServerError)
}))
defer server.Close()

tempDir := t.TempDir()
version := "5.2.2"
mockDownloadURL := fmt.Sprintf("%s/phpMyAdmin-%s-all-languages.zip", server.URL, version)

_, err := DownloadPhpMyAdmin(mockDownloadURL, tempDir, version)
if err == nil || !strings.Contains(err.Error(), "unexpected HTTP status") {
t.Errorf("expected HTTP status error, got %v", err)
}
}

func TestDownloadPhpMyAdmin_DirectoryNotWritable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/zip")
w.Write([]byte("PK\x03\x04 dummy zip content"))

Check failure on line 104 in internal/downloader/downloader_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `w.Write` is not checked (errcheck)

Check failure on line 104 in internal/downloader/downloader_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `w.Write` is not checked (errcheck)
}))
defer server.Close()

tempDir := t.TempDir()
if err := os.Chmod(tempDir, 0500); err != nil {
t.Fatalf("failed to chmod: %v", err)
}
defer os.Chmod(tempDir, 0700)

Check failure on line 112 in internal/downloader/downloader_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `os.Chmod` is not checked (errcheck)

Check failure on line 112 in internal/downloader/downloader_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `os.Chmod` is not checked (errcheck)

version := "5.2.2"
mockDownloadURL := fmt.Sprintf("%s/phpMyAdmin-%s-all-languages.zip", server.URL, version)

_, err := DownloadPhpMyAdmin(mockDownloadURL, tempDir, version)
if err == nil {
t.Errorf("expected permission error, got none")
}
}
107 changes: 65 additions & 42 deletions internal/extractor/extractor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,35 @@
"archive/zip"
"os"
"path/filepath"
"strings"
"testing"
)

func TestExtractZip(t *testing.T) {
func TestExtractZip_Success(t *testing.T) {
tempDir := t.TempDir()

// Create test zip file
zipPath := filepath.Join(tempDir, "test.zip")
testFiles := map[string]string{
"folder1/file1.txt": "content1",
"folder2/file2.txt": "content2",
}

createErr := createTestZip(t, zipPath, testFiles)
if createErr != nil {
t.Fatalf("failed to create test zip: %v", createErr)
if err := createTestZip(t, zipPath, testFiles); err != nil {
t.Fatalf("failed to create test zip: %v", err)
}

extractDir := filepath.Join(tempDir, "extracted")

extractErr := ExtractZip(zipPath, extractDir)
if extractErr != nil {
t.Fatalf("ExtractZip failed: %v", extractErr)
if err := ExtractZip(zipPath, extractDir); err != nil {
t.Fatalf("ExtractZip failed: %v", err)
}

for name, content := range testFiles {
extractedPath := filepath.Join(extractDir, name)
data, readErr := os.ReadFile(extractedPath)
if readErr != nil {
t.Errorf("failed to read extracted file %s: %v", name, readErr)
continue
path := filepath.Join(extractDir, name)
data, err := os.ReadFile(path)
if err != nil {
t.Errorf("failed to read extracted file %s: %v", name, err)
}
if string(data) != content {
t.Errorf("file content mismatch for %s: expected '%s', got '%s'", name, content, string(data))
t.Errorf("content mismatch for %s", name)
Comment thread
jsas4coding marked this conversation as resolved.
}
}
}
Expand All @@ -46,58 +41,86 @@
tempDir := t.TempDir()

t.Run("file not found", func(t *testing.T) {
invalidPath := filepath.Join(tempDir, "nonexistent.zip")
extractDir := filepath.Join(tempDir, "extracted1")

err := ExtractZip(invalidPath, extractDir)
err := ExtractZip(filepath.Join(tempDir, "nonexistent.zip"), extractDir)
if err == nil {
t.Errorf("expected error when opening nonexistent file, got nil")
t.Errorf("expected error for nonexistent file")
}
})

t.Run("corrupted zip file", func(t *testing.T) {
badZipPath := filepath.Join(tempDir, "bad.zip")
writeErr := os.WriteFile(badZipPath, []byte("not a real zip content"), 0644)
if writeErr != nil {
t.Fatalf("failed to write corrupted zip: %v", writeErr)
}

os.WriteFile(badZipPath, []byte("not a real zip content"), 0644)

Check failure on line 53 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `os.WriteFile` is not checked (errcheck)

Check failure on line 53 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `os.WriteFile` is not checked (errcheck)
extractDir := filepath.Join(tempDir, "extracted2")
err := ExtractZip(badZipPath, extractDir)
if err == nil {
t.Errorf("expected error when extracting corrupted zip, got nil")
t.Errorf("expected error for corrupted zip")
}
})

t.Run("empty zip path", func(t *testing.T) {
extractDir := filepath.Join(tempDir, "extracted3")
err := ExtractZip("", extractDir)
if err == nil || !strings.Contains(err.Error(), "empty zip path") {
t.Errorf("expected empty zip path error")
}
})

t.Run("empty destination", func(t *testing.T) {
zipPath := filepath.Join(tempDir, "test.zip")
testFiles := map[string]string{"file.txt": "data"}
createTestZip(t, zipPath, testFiles)

Check failure on line 72 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value is not checked (errcheck)

Check failure on line 72 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value is not checked (errcheck)
err := ExtractZip(zipPath, "")
if err == nil || !strings.Contains(err.Error(), "empty destination path") {
t.Errorf("expected empty destination path error")
}
})

t.Run("permission denied on destination", func(t *testing.T) {
zipPath := filepath.Join(tempDir, "test2.zip")
testFiles := map[string]string{"file.txt": "data"}
createTestZip(t, zipPath, testFiles)

Check failure on line 82 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value is not checked (errcheck)

Check failure on line 82 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value is not checked (errcheck)

extractDir := filepath.Join(tempDir, "extracted3")
os.MkdirAll(extractDir, 0500)

Check failure on line 85 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `os.MkdirAll` is not checked (errcheck)

Check failure on line 85 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `os.MkdirAll` is not checked (errcheck)
defer os.Chmod(extractDir, 0700)

Check failure on line 86 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `os.Chmod` is not checked (errcheck)

Check failure on line 86 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `os.Chmod` is not checked (errcheck)

err := ExtractZip(zipPath, extractDir)
if err == nil {
t.Errorf("expected permission error")
}
})

t.Run("invalid path traversal", func(t *testing.T) {
zipPath := filepath.Join(tempDir, "evil.zip")
createTestZip(t, zipPath, map[string]string{"../evil.txt": "attack"})

Check failure on line 96 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value is not checked (errcheck)

Check failure on line 96 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value is not checked (errcheck)
extractDir := filepath.Join(tempDir, "extracted4")
err := ExtractZip(zipPath, extractDir)
if err == nil || !strings.Contains(err.Error(), "invalid file path detected") {
t.Errorf("expected invalid path detection")
}
})
}

// Hardening helper - fully linter safe
func createTestZip(t *testing.T, zipPath string, files map[string]string) error {
zipFile, err := os.Create(zipPath)
if err != nil {
return err
t.Fatalf("failed to create zip file: %v", err)
Comment thread
jsas4coding marked this conversation as resolved.
}
defer func() {
if cerr := zipFile.Close(); cerr != nil {
t.Errorf("failed to close zipFile: %v", cerr)
}
}()
defer zipFile.Close()

Check failure on line 110 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `zipFile.Close` is not checked (errcheck)

Check failure on line 110 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `zipFile.Close` is not checked (errcheck)

zipWriter := zip.NewWriter(zipFile)
defer func() {
if cerr := zipWriter.Close(); cerr != nil {
t.Errorf("failed to close zipWriter: %v", cerr)
}
}()
defer zipWriter.Close()

Check failure on line 113 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / build

Error return value of `zipWriter.Close` is not checked (errcheck)

Check failure on line 113 in internal/extractor/extractor_test.go

View workflow job for this annotation

GitHub Actions / pr-check

Error return value of `zipWriter.Close` is not checked (errcheck)

for name, content := range files {
writer, err := zipWriter.Create(name)
if err != nil {
return err
t.Fatalf("failed to create entry in zip: %v", err)
}
if _, err := writer.Write([]byte(content)); err != nil {
return err
_, err = writer.Write([]byte(content))
if err != nil {
t.Fatalf("failed to write zip content: %v", err)
}
}

return nil
}
Loading