package soap import ( "bytes" "crypto/sha1" "encoding/base64" "encoding/xml" "io" "net/http" "net/http/httptest" "strings" "testing" ) const testXMLHeader = `` func TestNewHandler(t *testing.T) { handler := NewHandler("admin", "password") if handler == nil { t.Error("NewHandler returned nil") return } if handler.username != "admin" { t.Errorf("Username mismatch: got %s, want admin", handler.username) } if handler.password != "password" { t.Errorf("Password mismatch: got %s, want password", handler.password) } if handler.handlers == nil { t.Error("Handlers map is nil") } } func TestRegisterHandler(t *testing.T) { handler := NewHandler("admin", "password") testHandler := func(body interface{}) (interface{}, error) { return "test response", nil } handler.RegisterHandler("TestAction", testHandler) if _, ok := handler.handlers["TestAction"]; !ok { t.Error("Handler not registered") } } func TestServeHTTPMethodNotAllowed(t *testing.T) { handler := NewHandler("admin", "password") req := httptest.NewRequest("GET", "/", http.NoBody) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusMethodNotAllowed { t.Errorf("Expected status %d, got %d", http.StatusMethodNotAllowed, w.Code) } } func TestServeHTTPValidSOAPRequest(t *testing.T) { handler := NewHandler("", "") // No authentication // Create test handler handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return map[string]string{"Result": "Success"}, nil }) // Create SOAP request soapBody := testXMLHeader + ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code == http.StatusInternalServerError { t.Errorf("Handler returned error: %s", w.Body.String()) } } func TestServeHTTPInvalidSOAPEnvelope(t *testing.T) { handler := NewHandler("", "") invalidXML := ` not soap ` req := httptest.NewRequest("POST", "/", strings.NewReader(invalidXML)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) // Should return a SOAP fault if !strings.Contains(w.Body.String(), "Fault") { t.Errorf("Expected SOAP fault, got: %s", w.Body.String()) } } func TestServeHTTPUnknownAction(t *testing.T) { handler := NewHandler("", "") soapBody := ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if !strings.Contains(w.Body.String(), "Fault") { t.Errorf("Expected SOAP fault for unknown action") } } func TestExtractAction(t *testing.T) { handler := NewHandler("", "") tests := []struct { name string soapBody string expectedAction string }{ { name: "Simple action", soapBody: ` `, expectedAction: "GetDeviceInformation", }, { name: "Action with namespace", soapBody: ` `, expectedAction: "GetDeviceInformation", }, { name: "Action with attributes", soapBody: ` value `, expectedAction: "GetProfiles", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { action := handler.extractAction([]byte(tt.soapBody)) if action != tt.expectedAction { t.Errorf("Expected action %s, got %s", tt.expectedAction, action) } }) } } func TestExtractActionInvalid(t *testing.T) { handler := NewHandler("", "") invalidXML := "not valid xml at all" action := handler.extractAction([]byte(invalidXML)) if action != "" { t.Errorf("Expected empty action for invalid XML, got %s", action) } } func TestSendFault(t *testing.T) { handler := NewHandler("", "") w := httptest.NewRecorder() handler.sendFault(w, "Sender", "Test error", "Test error message") if w.Code != http.StatusBadRequest { t.Errorf("Expected status 400, got %d", w.Code) } response := w.Body.String() if !strings.Contains(response, "Fault") { t.Error("Response should contain Fault element") } if !strings.Contains(response, "Test error") { t.Error("Response should contain error message") } } func TestSendResponse(t *testing.T) { handler := NewHandler("", "") w := httptest.NewRecorder() response := map[string]string{ "Result": "Success", } handler.sendResponse(w, response) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } body := w.Body.String() if body == "" { t.Error("Response body is empty") } } func TestAuthenticate(t *testing.T) { handler := NewHandler("admin", "password") // Create a proper WS-Security header nonce := "test_nonce_12345" created := "2024-01-01T00:00:00Z" // Calculate digest hash := sha1.New() hash.Write([]byte(nonce)) hash.Write([]byte(created)) hash.Write([]byte("password")) digest := base64.StdEncoding.EncodeToString(hash.Sum(nil)) soapBody := ` admin ` + digest + ` ` + base64.StdEncoding.EncodeToString([]byte(nonce)) + ` ` + created + ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return "authenticated", nil }) handler.ServeHTTP(w, req) // Should succeed or indicate authentication was checked if w.Code == http.StatusInternalServerError && strings.Contains(w.Body.String(), "Authentication") { t.Logf("Authentication check passed (expected behavior)") } } func TestAuthenticateFailsWithWrongPassword(t *testing.T) { handler := NewHandler("admin", "correct_password") // Calculate digest with wrong password nonce := "test_nonce_12345" created := "2024-01-01T00:00:00Z" hash := sha1.New() hash.Write([]byte(nonce)) hash.Write([]byte(created)) hash.Write([]byte("wrong_password")) // Wrong password digest := base64.StdEncoding.EncodeToString(hash.Sum(nil)) soapBody := ` admin ` + digest + ` ` + base64.StdEncoding.EncodeToString([]byte(nonce)) + ` ` + created + ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return "should not reach here", nil }) handler.ServeHTTP(w, req) // Should fail authentication if !strings.Contains(w.Body.String(), "Fault") { t.Errorf("Expected authentication failure") } } func TestHandlerWithoutAuthentication(t *testing.T) { handler := NewHandler("", "") // No authentication soapBody := testXMLHeader + ` ` handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return "success", nil }) req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) // Should succeed without authentication if w.Code == http.StatusInternalServerError && strings.Contains(w.Body.String(), "Authentication") { t.Errorf("Should not require authentication when not configured") } } func TestReadRequestBodyError(t *testing.T) { handler := NewHandler("", "") // Create a request with a body that will fail to read req := httptest.NewRequest("POST", "/", &failingReader{}) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if !strings.Contains(w.Body.String(), "Fault") { t.Errorf("Expected SOAP fault for read error") } } // Helper types and functions type failingReader struct{} func (f *failingReader) Read(p []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } func TestResponseHandling(t *testing.T) { handler := NewHandler("", "") type TestResponse struct { XMLName xml.Name `xml:"TestActionResponse"` Result string `xml:"Result"` } handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return &TestResponse{Result: "Success"}, nil }) soapBody := ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } response := w.Body.String() if !strings.Contains(response, "TestActionResponse") { t.Errorf("Response should contain TestActionResponse element") } } func TestEmptyBody(t *testing.T) { handler := NewHandler("", "") req := httptest.NewRequest("POST", "/", bytes.NewReader([]byte(""))) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if !strings.Contains(w.Body.String(), "Fault") { t.Errorf("Expected SOAP fault for empty body") } } func TestContentType(t *testing.T) { handler := NewHandler("", "") handler.RegisterHandler("TestAction", func(body interface{}) (interface{}, error) { return "test", nil }) soapBody := ` ` req := httptest.NewRequest("POST", "/", strings.NewReader(soapBody)) req.Header.Set("Content-Type", "application/soap+xml") w := httptest.NewRecorder() handler.ServeHTTP(w, req) // Handler should work regardless of content type if w.Code == http.StatusInternalServerError { t.Logf("Note: Handler may validate content type") } }