You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
7.2 KiB
275 lines
7.2 KiB
package s3api
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsSOSAPIObject(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
object string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "system.xml should be detected",
|
|
object: ".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "capacity.xml should be detected",
|
|
object: ".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "regular object should not be detected",
|
|
object: "myfile.txt",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "similar but different path should not be detected",
|
|
object: ".system-other-uuid/system.xml",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "nested path should not be detected",
|
|
object: "prefix/.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "empty string should not be detected",
|
|
object: "",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isSOSAPIObject(tt.object)
|
|
if result != tt.expected {
|
|
t.Errorf("isSOSAPIObject(%q) = %v, want %v", tt.object, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsSOSAPIClient(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
userAgent string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Veeam backup client should be detected",
|
|
userAgent: "APN/1.0 Veeam/1.0 Backup/10.0",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "exact match should be detected",
|
|
userAgent: "APN/1.0 Veeam/1.0",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "AWS CLI should not be detected",
|
|
userAgent: "aws-cli/2.0.0 Python/3.8",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "empty user agent should not be detected",
|
|
userAgent: "",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "partial match should not be detected",
|
|
userAgent: "Veeam/1.0",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/bucket/object", nil)
|
|
req.Header.Set("User-Agent", tt.userAgent)
|
|
result := isSOSAPIClient(req)
|
|
if result != tt.expected {
|
|
t.Errorf("isSOSAPIClient() with User-Agent %q = %v, want %v", tt.userAgent, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateSystemXML(t *testing.T) {
|
|
xmlData, err := generateSystemXML()
|
|
if err != nil {
|
|
t.Fatalf("generateSystemXML() failed: %v", err)
|
|
}
|
|
|
|
// Verify it's valid XML
|
|
var si SystemInfo
|
|
if err := xml.Unmarshal(xmlData, &si); err != nil {
|
|
t.Fatalf("generated XML is invalid: %v", err)
|
|
}
|
|
|
|
// Verify required fields
|
|
if si.ProtocolVersion != sosAPIProtocolVersion {
|
|
t.Errorf("ProtocolVersion = %q, want %q", si.ProtocolVersion, sosAPIProtocolVersion)
|
|
}
|
|
|
|
if !strings.Contains(si.ModelName, "SeaweedFS") {
|
|
t.Errorf("ModelName = %q, should contain 'SeaweedFS'", si.ModelName)
|
|
}
|
|
|
|
if !si.ProtocolCapabilities.CapacityInfo {
|
|
t.Error("ProtocolCapabilities.CapacityInfo should be true")
|
|
}
|
|
|
|
if si.SystemRecommendations == nil {
|
|
t.Fatal("SystemRecommendations should not be nil")
|
|
}
|
|
|
|
if si.SystemRecommendations.KBBlockSize != sosAPIDefaultBlockSizeKB {
|
|
t.Errorf("KBBlockSize = %d, want %d", si.SystemRecommendations.KBBlockSize, sosAPIDefaultBlockSizeKB)
|
|
}
|
|
}
|
|
|
|
func TestCapacityInfoXMLStruct(t *testing.T) {
|
|
// Test that CapacityInfo can be marshaled correctly
|
|
ci := CapacityInfo{
|
|
Capacity: 1000000,
|
|
Available: 800000,
|
|
Used: 200000,
|
|
}
|
|
|
|
xmlData, err := xml.Marshal(&ci)
|
|
if err != nil {
|
|
t.Fatalf("xml.Marshal failed: %v", err)
|
|
}
|
|
|
|
// Verify roundtrip
|
|
var parsed CapacityInfo
|
|
if err := xml.Unmarshal(xmlData, &parsed); err != nil {
|
|
t.Fatalf("xml.Unmarshal failed: %v", err)
|
|
}
|
|
|
|
if parsed.Capacity != ci.Capacity {
|
|
t.Errorf("Capacity = %d, want %d", parsed.Capacity, ci.Capacity)
|
|
}
|
|
if parsed.Available != ci.Available {
|
|
t.Errorf("Available = %d, want %d", parsed.Available, ci.Available)
|
|
}
|
|
if parsed.Used != ci.Used {
|
|
t.Errorf("Used = %d, want %d", parsed.Used, ci.Used)
|
|
}
|
|
}
|
|
|
|
func TestSOSAPIConstants(t *testing.T) {
|
|
// Verify constants are correctly set
|
|
if !strings.HasPrefix(sosAPISystemXML, sosAPISystemFolder) {
|
|
t.Errorf("sosAPISystemXML should start with sosAPISystemFolder")
|
|
}
|
|
|
|
if !strings.HasPrefix(sosAPICapacityXML, sosAPISystemFolder) {
|
|
t.Errorf("sosAPICapacityXML should start with sosAPISystemFolder")
|
|
}
|
|
|
|
if !strings.HasSuffix(sosAPISystemXML, "system.xml") {
|
|
t.Errorf("sosAPISystemXML should end with 'system.xml'")
|
|
}
|
|
|
|
if !strings.HasSuffix(sosAPICapacityXML, "capacity.xml") {
|
|
t.Errorf("sosAPICapacityXML should end with 'capacity.xml'")
|
|
}
|
|
|
|
// Protocol version should be quoted per SOSAPI spec
|
|
if !strings.HasPrefix(sosAPIProtocolVersion, "\"") || !strings.HasSuffix(sosAPIProtocolVersion, "\"") {
|
|
t.Errorf("sosAPIProtocolVersion should be quoted, got: %s", sosAPIProtocolVersion)
|
|
}
|
|
}
|
|
|
|
func TestSystemInfoXMLRootElement(t *testing.T) {
|
|
xmlData, err := generateSystemXML()
|
|
if err != nil {
|
|
t.Fatalf("generateSystemXML() failed: %v", err)
|
|
}
|
|
|
|
xmlStr := string(xmlData)
|
|
|
|
// Verify root element name
|
|
if !strings.Contains(xmlStr, "<SystemInfo>") {
|
|
t.Error("XML should contain <SystemInfo> root element")
|
|
}
|
|
|
|
// Verify required elements
|
|
requiredElements := []string{
|
|
"<ProtocolVersion>",
|
|
"<ModelName>",
|
|
"<ProtocolCapabilities>",
|
|
"<CapacityInfo>",
|
|
}
|
|
|
|
for _, elem := range requiredElements {
|
|
if !strings.Contains(xmlStr, elem) {
|
|
t.Errorf("XML should contain %s element", elem)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSOSAPIHandlerIntegration tests the basic handler flow without a full server
|
|
func TestSOSAPIObjectDetectionEdgeCases(t *testing.T) {
|
|
// Test various edge cases for object detection
|
|
edgeCases := []struct {
|
|
object string
|
|
expected bool
|
|
}{
|
|
// With leading slash
|
|
{"/.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml", false},
|
|
// URL encoded
|
|
{".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c%2Fsystem.xml", false},
|
|
// Mixed case
|
|
{".System-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml", false},
|
|
// Extra slashes
|
|
{".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c//system.xml", false},
|
|
// Correct paths
|
|
{".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml", true},
|
|
{".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml", true},
|
|
}
|
|
|
|
for _, tc := range edgeCases {
|
|
result := isSOSAPIObject(tc.object)
|
|
if result != tc.expected {
|
|
t.Errorf("isSOSAPIObject(%q) = %v, want %v", tc.object, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSOSAPIHandlerReturnsXMLContentType verifies content-type setting logic
|
|
func TestSOSAPIXMLContentType(t *testing.T) {
|
|
// Create a mock response writer to check headers
|
|
w := httptest.NewRecorder()
|
|
|
|
// Simulate what the handler should set
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
contentType := w.Header().Get("Content-Type")
|
|
if contentType != "application/xml" {
|
|
t.Errorf("Content-Type = %q, want 'application/xml'", contentType)
|
|
}
|
|
}
|
|
|
|
func TestHTTPTimeFormat(t *testing.T) {
|
|
// Verify the Last-Modified header format is correct for HTTP
|
|
w := httptest.NewRecorder()
|
|
w.Header().Set("Last-Modified", "Sat, 28 Dec 2024 20:00:00 GMT")
|
|
|
|
lastMod := w.Header().Get("Last-Modified")
|
|
if lastMod == "" {
|
|
t.Error("Last-Modified header should be set")
|
|
}
|
|
|
|
// HTTP date should contain day of week
|
|
if !strings.Contains(lastMod, "Dec") {
|
|
t.Errorf("Last-Modified should contain month, got: %s", lastMod)
|
|
}
|
|
}
|