mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Add comprehensive tests across all Go packages: - auth: username validation, concurrent rate limiting, TOTP stores, timing - config: env overrides, default credential detection, voice defaults - db: search, message queries, special char handling - api: handler edge cases, error paths, DM/invite/TOTP coverage - ws: voice handler paths, integration scenarios - updater: version comparison, timeout handling 6 of 8 packages now at 80%+ coverage.
477 lines
14 KiB
Go
477 lines
14 KiB
Go
package storage_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/owncord/server/storage"
|
|
)
|
|
|
|
// newTestStorage creates a Storage instance backed by a temporary directory
|
|
// that is removed when the test ends.
|
|
func newTestStorage(t *testing.T) *storage.Storage {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
s, err := storage.New(dir, 10)
|
|
if err != nil {
|
|
t.Fatalf("storage.New: %v", err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ─── sanitizeFilename / path validation (tested indirectly via Save/Delete/Open) ─
|
|
|
|
// TestSave_ValidUUID verifies that a normal UUID-style filename is accepted.
|
|
func TestSave_ValidUUID(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("550e8400-e29b-41d4-a716-446655440000", strings.NewReader("hello"))
|
|
if err != nil {
|
|
t.Errorf("Save valid uuid: unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSave_PathTraversalDotDot rejects filenames containing "..".
|
|
func TestSave_PathTraversalDotDot(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("../../etc/passwd", strings.NewReader("evil"))
|
|
if err == nil {
|
|
t.Error("Save('../../etc/passwd') returned nil error, want path traversal error")
|
|
}
|
|
}
|
|
|
|
// TestSave_DotDotFilename rejects the literal string "..".
|
|
func TestSave_DotDotFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("..", strings.NewReader("evil"))
|
|
if err == nil {
|
|
t.Error("Save('..') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_SingleDotFilename rejects the literal string ".".
|
|
func TestSave_SingleDotFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save(".", strings.NewReader("evil"))
|
|
if err == nil {
|
|
t.Error("Save('.') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_EmptyFilename rejects an empty string.
|
|
func TestSave_EmptyFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("", strings.NewReader("data"))
|
|
if err == nil {
|
|
t.Error("Save('') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_DotPrefixFilename rejects filenames starting with ".".
|
|
func TestSave_DotPrefixFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save(".hidden", strings.NewReader("data"))
|
|
if err == nil {
|
|
t.Error("Save('.hidden') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_ForwardSlashRejected rejects filenames containing a forward slash.
|
|
func TestSave_ForwardSlashRejected(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("sub/file", strings.NewReader("data"))
|
|
if err == nil {
|
|
t.Error("Save('sub/file') returned nil error, want path separator error")
|
|
}
|
|
}
|
|
|
|
// TestSave_BackslashRejected rejects filenames containing a backslash.
|
|
func TestSave_BackslashRejected(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save(`sub\file`, strings.NewReader("data"))
|
|
if err == nil {
|
|
t.Error(`Save('sub\file') returned nil error, want path separator error`)
|
|
}
|
|
}
|
|
|
|
// TestSave_ResolvedPathStaysInDir verifies the stored file is actually inside
|
|
// the storage directory (defence-in-depth after sanitisation).
|
|
func TestSave_ResolvedPathStaysInDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, _ := storage.New(dir, 10)
|
|
|
|
filename := "valid-file.dat"
|
|
if err := s.Save(filename, strings.NewReader("content")); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
expectedPath := filepath.Join(dir, filename)
|
|
if _, err := os.Stat(expectedPath); errors.Is(err, os.ErrNotExist) {
|
|
t.Errorf("expected file at %s but it was not found", expectedPath)
|
|
}
|
|
}
|
|
|
|
// TestDelete_ValidUUID verifies that a saved file can be deleted by its UUID.
|
|
func TestDelete_ValidUUID(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if err := s.Save("abc123", strings.NewReader("data")); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
if err := s.Delete("abc123"); err != nil {
|
|
t.Errorf("Delete valid uuid: unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestDelete_PathTraversal rejects path-traversal filenames.
|
|
func TestDelete_PathTraversal(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Delete("../../sensitive")
|
|
if err == nil {
|
|
t.Error("Delete('../../sensitive') returned nil error, want path traversal error")
|
|
}
|
|
}
|
|
|
|
// TestDelete_DotDot rejects "..".
|
|
func TestDelete_DotDot(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if err := s.Delete(".."); err == nil {
|
|
t.Error("Delete('..') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestDelete_EmptyFilename rejects an empty string.
|
|
func TestDelete_EmptyFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if err := s.Delete(""); err == nil {
|
|
t.Error("Delete('') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestDelete_DotPrefixFilename rejects filenames starting with ".".
|
|
func TestDelete_DotPrefixFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if err := s.Delete(".hidden"); err == nil {
|
|
t.Error("Delete('.hidden') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestOpen_ValidUUID verifies that a saved file can be opened and read back.
|
|
func TestOpen_ValidUUID(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
content := "hello storage"
|
|
if err := s.Save("myfile", strings.NewReader(content)); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
f, err := s.Open("myfile")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
got, err := io.ReadAll(f)
|
|
if err != nil {
|
|
t.Fatalf("reading opened file: %v", err)
|
|
}
|
|
if string(got) != content {
|
|
t.Errorf("content = %q, want %q", got, content)
|
|
}
|
|
}
|
|
|
|
// TestOpen_PathTraversal rejects path-traversal filenames.
|
|
func TestOpen_PathTraversal(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
_, err := s.Open("../../etc/passwd")
|
|
if err == nil {
|
|
t.Error("Open('../../etc/passwd') returned nil error, want path traversal error")
|
|
}
|
|
}
|
|
|
|
// TestOpen_DotDot rejects "..".
|
|
func TestOpen_DotDot(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if _, err := s.Open(".."); err == nil {
|
|
t.Error("Open('..') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestOpen_EmptyFilename rejects an empty string.
|
|
func TestOpen_EmptyFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if _, err := s.Open(""); err == nil {
|
|
t.Error("Open('') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestOpen_DotPrefixFilename rejects filenames starting with ".".
|
|
func TestOpen_DotPrefixFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if _, err := s.Open(".env"); err == nil {
|
|
t.Error("Open('.env') returned nil error, want error")
|
|
}
|
|
}
|
|
|
|
// TestOpen_ForwardSlashRejected rejects filenames containing a forward slash.
|
|
func TestOpen_ForwardSlashRejected(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
if _, err := s.Open("dir/file"); err == nil {
|
|
t.Error("Open('dir/file') returned nil error, want path separator error")
|
|
}
|
|
}
|
|
|
|
// TestSave_RoundTrip confirms data integrity through Save then Open.
|
|
func TestSave_RoundTrip(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
payload := bytes.Repeat([]byte("abcdef"), 1000) // 6 KB
|
|
if err := s.Save("roundtrip", bytes.NewReader(payload)); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
f, err := s.Open("roundtrip")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
got, _ := io.ReadAll(f)
|
|
if !bytes.Equal(got, payload) {
|
|
t.Errorf("round-trip data mismatch: got %d bytes, want %d", len(got), len(payload))
|
|
}
|
|
}
|
|
|
|
// ─── 4.2: Magic byte validation ───────────────────────────────────────────────
|
|
|
|
// TestValidateFileType_AllowsNormalContent verifies that plain file content passes.
|
|
func TestValidateFileType_AllowsNormalContent(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
header []byte
|
|
}{
|
|
{"PNG", []byte("\x89PNG\r\n\x1a\n")},
|
|
{"JPEG", []byte("\xff\xd8\xff\xe0")},
|
|
{"GIF87", []byte("GIF87a")},
|
|
{"GIF89", []byte("GIF89a")},
|
|
{"PDF", []byte("%PDF-1.4")},
|
|
{"ZIP", []byte("PK\x03\x04")},
|
|
{"plaintext", []byte("Hello world")},
|
|
{"empty", []byte{}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := storage.ValidateFileType(tc.header)
|
|
if err != nil {
|
|
t.Errorf("ValidateFileType(%q) = %v, want nil", tc.name, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_BlocksPEExecutable verifies Windows .exe files are rejected.
|
|
func TestValidateFileType_BlocksPEExecutable(t *testing.T) {
|
|
header := []byte("MZP\x00\x02\x00\x00\x00") // PE magic "MZ"
|
|
err := storage.ValidateFileType(header)
|
|
if err == nil {
|
|
t.Error("ValidateFileType(PE header) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_BlocksELFBinary verifies Linux ELF binaries are rejected.
|
|
func TestValidateFileType_BlocksELFBinary(t *testing.T) {
|
|
header := []byte("\x7fELF\x02\x01\x01\x00")
|
|
err := storage.ValidateFileType(header)
|
|
if err == nil {
|
|
t.Error("ValidateFileType(ELF header) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_BlocksMachO64 verifies macOS 64-bit Mach-O binaries are rejected.
|
|
func TestValidateFileType_BlocksMachO64(t *testing.T) {
|
|
header := []byte("\xcf\xfa\xed\xfe\x07\x00\x00\x01")
|
|
err := storage.ValidateFileType(header)
|
|
if err == nil {
|
|
t.Error("ValidateFileType(Mach-O 64 header) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_BlocksMachO32 verifies macOS 32-bit Mach-O binaries are rejected.
|
|
func TestValidateFileType_BlocksMachO32(t *testing.T) {
|
|
header := []byte("\xce\xfa\xed\xfe\x07\x00\x00\x01")
|
|
err := storage.ValidateFileType(header)
|
|
if err == nil {
|
|
t.Error("ValidateFileType(Mach-O 32 header) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_BlocksShellScript verifies shebang scripts are rejected.
|
|
func TestValidateFileType_BlocksShellScript(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
header []byte
|
|
}{
|
|
{"bash", []byte("#!/bin/bash\necho hi")},
|
|
{"sh", []byte("#!/bin/sh\necho hi")},
|
|
{"python", []byte("#!/usr/bin/env python3\nprint('x')")},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := storage.ValidateFileType(tc.header)
|
|
if err == nil {
|
|
t.Errorf("ValidateFileType(script %q) = nil, want error", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateFileType_ErrorMessageContainsFormat verifies the error names the blocked type.
|
|
func TestValidateFileType_ErrorMessageContainsFormat(t *testing.T) {
|
|
header := []byte("MZ\x90\x00") // PE executable
|
|
err := storage.ValidateFileType(header)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "PE executable") {
|
|
t.Errorf("error message %q does not mention 'PE executable'", err.Error())
|
|
}
|
|
}
|
|
|
|
// TestSave_BlocksExecutable verifies Save rejects PE executable content.
|
|
func TestSave_BlocksExecutable(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
// Construct content with PE magic followed by padding.
|
|
content := append([]byte("MZ"), make([]byte, 100)...)
|
|
err := s.Save("malware.exe", bytes.NewReader(content))
|
|
if err == nil {
|
|
t.Error("Save(PE executable) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_BlocksELF verifies Save rejects ELF binary content.
|
|
func TestSave_BlocksELF(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
content := append([]byte("\x7fELF"), make([]byte, 100)...)
|
|
err := s.Save("linux-binary", bytes.NewReader(content))
|
|
if err == nil {
|
|
t.Error("Save(ELF binary) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_BlocksShellScript verifies Save rejects script content.
|
|
func TestSave_BlocksShellScript(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
content := []byte("#!/bin/bash\nrm -rf /\n")
|
|
err := s.Save("nasty.sh", bytes.NewReader(content))
|
|
if err == nil {
|
|
t.Error("Save(shell script) = nil, want error")
|
|
}
|
|
}
|
|
|
|
// TestSave_AllowsPNG verifies Save still accepts legitimate image content after magic check.
|
|
func TestSave_AllowsPNG(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
content := append([]byte("\x89PNG\r\n\x1a\n"), make([]byte, 100)...)
|
|
err := s.Save("image.png", bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Errorf("Save(PNG) = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
// TestSave_EmptyFileAllowed verifies that an empty file (no content) is accepted.
|
|
func TestSave_EmptyFileAllowed(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("empty-file", bytes.NewReader([]byte{}))
|
|
if err != nil {
|
|
t.Errorf("Save(empty) = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
// ─── New edge cases ──────────────────────────────────────────────────────────
|
|
|
|
func TestNew_CreatesDirectory(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
newDir := filepath.Join(tmpDir, "nested", "storage")
|
|
|
|
s, err := storage.New(newDir, 10)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
if s == nil {
|
|
t.Fatal("New returned nil")
|
|
}
|
|
|
|
// Directory should exist.
|
|
info, statErr := os.Stat(newDir)
|
|
if statErr != nil {
|
|
t.Fatalf("directory not created: %v", statErr)
|
|
}
|
|
if !info.IsDir() {
|
|
t.Error("expected directory, got file")
|
|
}
|
|
}
|
|
|
|
// ─── Save large file ────────────────────────────────────────────────────────
|
|
|
|
func TestSave_ExceedsMaxSize(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
// 1 MB max.
|
|
s, err := storage.New(tmpDir, 1)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
|
|
// Create reader with >1MB of data.
|
|
bigData := bytes.Repeat([]byte("x"), 1024*1024+100)
|
|
err = s.Save("big-file", bytes.NewReader(bigData))
|
|
if err == nil {
|
|
t.Error("Save should reject file exceeding max size")
|
|
}
|
|
|
|
// File should be removed.
|
|
if _, statErr := os.Stat(filepath.Join(tmpDir, "big-file")); !os.IsNotExist(statErr) {
|
|
t.Error("oversized file should be removed after rejection")
|
|
}
|
|
}
|
|
|
|
func TestSave_ReadError(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save("read-err", &failReader{})
|
|
if err == nil {
|
|
t.Error("Save with failing reader should return error")
|
|
}
|
|
}
|
|
|
|
type failReader struct{}
|
|
|
|
func (f *failReader) Read([]byte) (int, error) {
|
|
return 0, errors.New("simulated read error")
|
|
}
|
|
|
|
// ─── resolvedPath edge case (via Save with dot prefix) ──────────────────────
|
|
|
|
func TestSave_HiddenFilename(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Save(".hidden", strings.NewReader("data"))
|
|
if err == nil {
|
|
t.Error("Save should reject hidden filenames starting with '.'")
|
|
}
|
|
}
|
|
|
|
func TestOpen_NotFound(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
_, err := s.Open("nonexistent-file")
|
|
if err == nil {
|
|
t.Error("Open should return error for nonexistent file")
|
|
}
|
|
}
|
|
|
|
func TestDelete_NotFound(t *testing.T) {
|
|
s := newTestStorage(t)
|
|
err := s.Delete("nonexistent-file")
|
|
if err == nil {
|
|
t.Error("Delete should return error for nonexistent file")
|
|
}
|
|
}
|