Persist build extra_files for Build Manager history, print dashboard login on every start, add libp2p for Mesh P2P forge, defer WebSocket until login, and split devrun.bat from LAUNCH.bat with USB deck auto-detection.
260 lines
6.8 KiB
Go
260 lines
6.8 KiB
Go
package deploy
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestXMLEscape(t *testing.T) {
|
|
got := xmlEscape(`a&b<c>d`)
|
|
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 = `<?xml version="1.0"?>
|
|
<root>
|
|
<device>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
|
|
<controlURL>/ctl/IPConn</controlURL>
|
|
</service>
|
|
</serviceList>
|
|
</device>
|
|
</root>`
|
|
|
|
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 = `<?xml version="1.0"?>
|
|
<root>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType>
|
|
<controlURL>http://192.168.0.1:49152/ctl/IPConn</controlURL>
|
|
</service>
|
|
</root>`
|
|
|
|
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, `<root><service><controlURL>/x</controlURL></service></root>`)
|
|
}))
|
|
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, `<root><serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType></root>`)
|
|
}))
|
|
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 = `<?xml version="1.0"?>
|
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<s:Body>
|
|
<u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
|
|
<NewExternalIPAddress>203.0.113.10</NewExternalIPAddress>
|
|
</u:GetExternalIPAddressResponse>
|
|
</s:Body>
|
|
</s:Envelope>`
|
|
|
|
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, `<errorCode>718</errorCode>`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
_, err := upnpSOAP(srv.URL, "AddPortMapping", "<body/>")
|
|
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 = `<?xml version="1.0"?>
|
|
<root>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
|
|
<controlURL>ctl/IPConn</controlURL>
|
|
</service>
|
|
</root>`
|
|
|
|
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, `<response/>`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
if _, err := upnpSOAP(srv.URL, "GetExternalIPAddress", "<body/>"); 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, `<?xml version="1.0"?><ok/>`)
|
|
}))
|
|
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, "<NewExternalPort>8989</NewExternalPort>") {
|
|
t.Fatalf("body missing port: %q", gotBody)
|
|
}
|
|
if !strings.Contains(gotBody, "<NewProtocol>TCP</NewProtocol>") {
|
|
t.Fatalf("body missing protocol: %q", gotBody)
|
|
}
|
|
}
|