package deploy import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func TestXMLEscape(t *testing.T) { got := xmlEscape(`a&bd`) want := "a&b<c>d" if got != want { t.Fatalf("got %q want %q", got, want) } } func TestIntSliceStr(t *testing.T) { got := intSliceStr([]int{22, 445, 5985}) want := []string{"22", "445", "5985"} if len(got) != len(want) { t.Fatalf("len %d != %d", len(got), len(want)) } for i := range want { if got[i] != want[i] { t.Fatalf("[%d] got %q want %q", i, got[i], want[i]) } } } func TestGetSubnet(t *testing.T) { if got := getSubnet("192.168.1.42"); got != "192.168.1" { t.Fatalf("got %q", got) } if getSubnet("bad") != "" { t.Fatal("invalid ip should return empty") } if getSubnet("10.0.0.1") != "10.0.0" { t.Fatalf("got %q", getSubnet("10.0.0.1")) } } func TestCloseUPnPInvalidPort(t *testing.T) { _, err := CloseUPnP(0) if err == nil || !strings.Contains(err.Error(), "external port required") { t.Fatalf("expected port error, got %v", err) } } func TestResolveWANControlURL(t *testing.T) { const igdXML = ` urn:schemas-upnp-org:service:WANIPConnection:1 /ctl/IPConn ` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, igdXML) })) defer srv.Close() got, err := resolveWANControlURL(srv.URL + "/igd.xml") if err != nil { t.Fatal(err) } want := srv.URL + "/ctl/IPConn" if got != want { t.Fatalf("got %q want %q", got, want) } } func TestResolveWANControlURLAbsolute(t *testing.T) { const igdXML = ` urn:schemas-upnp-org:service:WANPPPConnection:1 http://192.168.0.1:49152/ctl/IPConn ` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, igdXML) })) defer srv.Close() got, err := resolveWANControlURL(srv.URL + "/desc.xml") if err != nil { t.Fatal(err) } if got != "http://192.168.0.1:49152/ctl/IPConn" { t.Fatalf("got %q", got) } } func TestResolveWANControlURLMissingService(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, `/x`) })) defer srv.Close() _, err := resolveWANControlURL(srv.URL) if err == nil || !strings.Contains(err.Error(), "WANIPConnection service not found") { t.Fatalf("expected service error, got %v", err) } } func TestResolveWANControlURLMissingControlURL(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, `urn:schemas-upnp-org:service:WANIPConnection:1`) })) defer srv.Close() _, err := resolveWANControlURL(srv.URL) if err == nil || !strings.Contains(err.Error(), "controlURL not found") { t.Fatalf("expected controlURL error, got %v", err) } } func TestUpnpGetExternalIP(t *testing.T) { const soapResp = ` 203.0.113.10 ` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } fmt.Fprint(w, soapResp) })) defer srv.Close() ip, err := upnpGetExternalIP(srv.URL) if err != nil { t.Fatal(err) } if ip != "203.0.113.10" { t.Fatalf("got %q", ip) } } func TestUpnpSOAPErrorResponse(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, `718`) })) defer srv.Close() _, err := upnpSOAP(srv.URL, "AddPortMapping", "") if err == nil || !strings.Contains(err.Error(), "AddPortMapping failed") { t.Fatalf("expected SOAP error, got %v", err) } } func TestReLocationParsesSSDPResponse(t *testing.T) { cases := []struct { body string want string }{ { "HTTP/1.1 200 OK\r\nLOCATION: http://192.168.0.1:49152/desc.xml\r\n\r\n", "http://192.168.0.1:49152/desc.xml", }, { "location: http://10.0.0.1/igd.xml", "http://10.0.0.1/igd.xml", }, } for _, tc := range cases { m := reLocation.FindStringSubmatch(tc.body) if len(m) != 2 { t.Fatalf("no LOCATION match in %q", tc.body) } if got := strings.TrimSpace(m[1]); got != tc.want { t.Fatalf("got %q want %q", got, tc.want) } } } func TestResolveWANControlURLRelativeWithoutLeadingSlash(t *testing.T) { const igdXML = ` urn:schemas-upnp-org:service:WANIPConnection:1 ctl/IPConn ` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, igdXML) })) defer srv.Close() got, err := resolveWANControlURL(srv.URL + "/igd.xml") if err != nil { t.Fatal(err) } want := srv.URL + "/ctl/IPConn" if got != want { t.Fatalf("got %q want %q", got, want) } } func TestUpnpSOAPRequestHeaders(t *testing.T) { var contentType, soapAction string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { contentType = r.Header.Get("Content-Type") soapAction = r.Header.Get("SOAPAction") fmt.Fprint(w, ``) })) defer srv.Close() if _, err := upnpSOAP(srv.URL, "GetExternalIPAddress", ""); err != nil { t.Fatal(err) } if !strings.Contains(contentType, "text/xml") { t.Fatalf("Content-Type: %q", contentType) } wantAction := `"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"` if soapAction != wantAction { t.Fatalf("SOAPAction: got %q want %q", soapAction, wantAction) } } func TestUpnpDeletePortMapping(t *testing.T) { var gotBody, gotAction string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAction = r.Header.Get("SOAPAction") buf := make([]byte, 4096) n, _ := r.Body.Read(buf) gotBody = string(buf[:n]) fmt.Fprint(w, ``) })) defer srv.Close() if err := upnpDeletePortMapping(srv.URL, 8989); err != nil { t.Fatal(err) } if !strings.Contains(gotAction, "DeletePortMapping") { t.Fatalf("SOAPAction: %q", gotAction) } if !strings.Contains(gotBody, "8989") { t.Fatalf("body missing port: %q", gotBody) } if !strings.Contains(gotBody, "TCP") { t.Fatalf("body missing protocol: %q", gotBody) } }