310 lines
8.2 KiB
Go
310 lines
8.2 KiB
Go
package media
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
func TestMatchCORSOrigin(t *testing.T) {
|
|
cases := []struct {
|
|
allowed []string
|
|
origin string
|
|
expected string
|
|
expectError bool
|
|
errorMessage string
|
|
}{
|
|
{
|
|
allowed: []string{"https://example.com"},
|
|
origin: "https://example.com",
|
|
expected: "https://example.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://example2.com", "https://example.com"},
|
|
origin: "https://example.com",
|
|
expected: "https://example.com",
|
|
},
|
|
{
|
|
allowed: []string{"*"},
|
|
origin: "https://example.com",
|
|
expected: "https://example.com",
|
|
},
|
|
{
|
|
allowed: []string{""},
|
|
origin: "https://example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{},
|
|
origin: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com"},
|
|
origin: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{},
|
|
origin: "https://example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"http://example.com"},
|
|
origin: "https://example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com"},
|
|
origin: "http://example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"http://example.com:8000"},
|
|
origin: "http://example.com:8000",
|
|
expected: "http://example.com:8000",
|
|
},
|
|
{
|
|
allowed: []string{"http://localhost:8090"},
|
|
origin: "http://localhost:8090",
|
|
expected: "http://localhost:8090",
|
|
},
|
|
{
|
|
allowed: []string{"http://localhost:8090"},
|
|
origin: "http://localhost:8080",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com"},
|
|
origin: "https://sub.example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.example.com"},
|
|
origin: "https://sub.example.com",
|
|
expected: "https://sub.example.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.example.com"},
|
|
origin: "https://pre.sub.example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.example.com", "https://*.sub.example.com"},
|
|
origin: "https://pre.sub.example.com",
|
|
expected: "https://pre.sub.example.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.*.example.com"},
|
|
origin: "https://pre.sub.example.com",
|
|
expected: "https://pre.sub.example.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.sub.example.com"},
|
|
origin: "https://pre.asd.example.com",
|
|
expected: "",
|
|
},
|
|
{
|
|
allowed: []string{"https://pre.*.example.com"},
|
|
origin: "https://pre.sub.example.com",
|
|
expected: "https://pre.sub.example.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.*.*.example.com"},
|
|
origin: "https://www.pre.sub.example.com",
|
|
expected: "https://www.pre.sub.example.com",
|
|
},
|
|
{
|
|
allowed: []string{"*"},
|
|
origin: "",
|
|
expected: "*", // Should allow any origin, including empty
|
|
},
|
|
// Error cases - these should fail during ParseCORSAllow
|
|
{
|
|
allowed: []string{"*", "https://example.com"},
|
|
origin: "https://example.com",
|
|
expectError: true,
|
|
errorMessage: "wildcard origin '*' must be the only entry",
|
|
},
|
|
{
|
|
allowed: []string{"not-a-valid-url"},
|
|
origin: "https://example.com",
|
|
expectError: true,
|
|
errorMessage: "invalid URI for request",
|
|
},
|
|
{
|
|
allowed: []string{"://invalid-url"},
|
|
origin: "https://example.com",
|
|
expectError: true,
|
|
errorMessage: "missing protocol scheme",
|
|
},
|
|
{
|
|
allowed: []string{"https://", "example.com"},
|
|
origin: "https://example.com",
|
|
expectError: true,
|
|
errorMessage: "invalid URI for request",
|
|
},
|
|
// Valid cases that should not error
|
|
{
|
|
allowed: []string{"", "https://example.com"},
|
|
origin: "https://example.com",
|
|
expectError: true,
|
|
errorMessage: "empty allowed origin '' must be the only entry", // Empty string should make all origins disallowed
|
|
},
|
|
{
|
|
allowed: []string{"https://Example.com"},
|
|
origin: "https://example.com",
|
|
expected: "", // Should not match due to case sensitivity
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com/"},
|
|
origin: "https://example.com",
|
|
expected: "", // Should not match due to trailing slash
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com"},
|
|
origin: "not-a-valid-url",
|
|
expected: "", // Should handle malformed origin gracefully
|
|
},
|
|
{
|
|
allowed: []string{"https://example.*.com"},
|
|
origin: "https://example.sub.com",
|
|
expected: "https://example.sub.com",
|
|
},
|
|
{
|
|
allowed: []string{"https://*.com"},
|
|
origin: "https://example.com",
|
|
expected: "https://example.com",
|
|
},
|
|
{
|
|
allowed: []string{"http://*.example.com"},
|
|
origin: "https://sub.example.com",
|
|
expected: "", // Should not match due to protocol difference
|
|
},
|
|
{
|
|
allowed: []string{"https://example.com", "https://example.com"},
|
|
origin: "https://example.com",
|
|
expected: "https://example.com", // Should still work with duplicates
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
allowedOrigins, err := ParseCORSAllow(tc.allowed)
|
|
|
|
if tc.expectError {
|
|
if err == nil {
|
|
t.Errorf("Test case %d: Expected error but got none. Allowed: %v", i, tc.allowed)
|
|
continue
|
|
}
|
|
if tc.errorMessage != "" && !containsSubstring(err.Error(), tc.errorMessage) {
|
|
t.Errorf("Test case %d: Expected error containing '%s', got '%s'", i, tc.errorMessage, err.Error())
|
|
}
|
|
// For error cases, we don't test the matching logic
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Test case %d: Unexpected error parsing allowed origins: %v. Allowed: %v", i, err, tc.allowed)
|
|
continue
|
|
}
|
|
|
|
result := matchCORSOrigin(allowedOrigins, tc.origin)
|
|
if result != tc.expected {
|
|
t.Errorf("Test case %d: Match CORS origin got wrong result. Expected '%s', got '%s'. Allowed: %v, Origin: '%s'",
|
|
i, tc.expected, result, tc.allowed, tc.origin)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function to check if a string contains a substring (case-insensitive)
|
|
func containsSubstring(str, substr string) bool {
|
|
return strings.Contains(strings.ToLower(str), strings.ToLower(substr))
|
|
}
|
|
|
|
func TestExtensionByMimeType(t *testing.T) {
|
|
cases := map[string]string{
|
|
"audio/x-m4a": ".m4a",
|
|
"audio/x-m4a; charset=utf": ".m4a",
|
|
"audio/mp4": ".m4a",
|
|
"audio/aac": ".aac",
|
|
"audio/mpeg": ".mp3",
|
|
"image/jpeg": ".jpg",
|
|
"image/jfif": ".jfif",
|
|
"video/mp4": ".mp4",
|
|
}
|
|
for mimeType, want := range cases {
|
|
if got := ExtensionByMimeType(mimeType); got != want {
|
|
t.Fatalf("ExtensionByMimeType(%q)=%q, want %q", mimeType, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMimeTypeForUploadPrefersContainerFilename(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
detect string
|
|
client string
|
|
filename string
|
|
want string
|
|
}{
|
|
{
|
|
name: "m4a misdetected as mp4",
|
|
detect: "video/mp4",
|
|
filename: "voice.m4a",
|
|
want: "audio/x-m4a",
|
|
},
|
|
{
|
|
name: "xlsx misdetected as zip",
|
|
detect: "application/zip",
|
|
filename: "iOS任务拆解.xlsx",
|
|
want: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
},
|
|
{
|
|
name: "client content type fallback",
|
|
detect: "application/octet-stream",
|
|
client: "image/jfif",
|
|
filename: "thumb",
|
|
want: "image/jfif",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := MimeTypeForUpload(tc.detect, tc.client, tc.filename); got != tc.want {
|
|
t.Fatalf("MimeTypeForUpload()=%q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFileURLNamePrefersOriginalExtension(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
def *types.FileDef
|
|
want string
|
|
}{
|
|
{
|
|
name: "voice keeps m4a extension",
|
|
def: &types.FileDef{ObjHeader: types.ObjHeader{Id: "file123"}, MimeType: "video/mp4", OriginalName: "voice.m4a"},
|
|
want: "file123.m4a",
|
|
},
|
|
{
|
|
name: "xlsx keeps xlsx extension",
|
|
def: &types.FileDef{ObjHeader: types.ObjHeader{Id: "sheet123"}, MimeType: "application/zip", OriginalName: "iOS任务拆解.xlsx"},
|
|
want: "sheet123.xlsx",
|
|
},
|
|
{
|
|
name: "falls back to mime extension",
|
|
def: &types.FileDef{ObjHeader: types.ObjHeader{Id: "image123"}, MimeType: "image/jpeg"},
|
|
want: "image123.jpg",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := FileURLName(tc.def); got != tc.want {
|
|
t.Fatalf("FileURLName()=%q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|