package tun2 import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "net" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "github.com/Xe/uuid" ) // testing constants const ( user = "shachi" token = "orcaz r kewl" noPermToken = "aw heck" otherUserToken = "even more heck" domain = "cetacean.club" ) func TestNewServerNullConfig(t *testing.T) { _, err := NewServer(nil) if err == nil { t.Fatalf("expected NewServer(nil) to fail, got non-failure") } } func TestGen502Page(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() req, err := http.NewRequest("GET", "http://cetacean.club", nil) if err != nil { t.Fatal(err) } substring := uuid.New() req = req.WithContext(ctx) req.Header.Add("X-Request-Id", substring) req.Host = "cetacean.club" resp := gen502Page(req) if resp == nil { t.Fatalf("expected response to be non-nil") } if resp.Body != nil { defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), substring) { fmt.Println(string(data)) t.Fatalf("502 page did not contain needed substring %q", substring) } } } func TestBackendAuthV1(t *testing.T) { st := MockStorage() s, err := NewServer(&ServerConfig{ Storage: st, }) if err != nil { t.Fatal(err) } defer s.Close() st.AddRoute(domain, user) st.AddToken(token, user, []string{"connect"}) st.AddToken(noPermToken, user, nil) st.AddToken(otherUserToken, "cadey", []string{"connect"}) cases := []struct { name string auth Auth wantErr bool }{ { name: "basic everything should work", auth: Auth{ Token: token, Domain: domain, }, wantErr: false, }, { name: "invalid domain", auth: Auth{ Token: token, Domain: "aw.heck", }, wantErr: true, }, { name: "invalid token", auth: Auth{ Token: "asdfwtweg", Domain: domain, }, wantErr: true, }, { name: "invalid token scopes", auth: Auth{ Token: noPermToken, Domain: domain, }, wantErr: true, }, { name: "user token doesn't match domain owner", auth: Auth{ Token: otherUserToken, Domain: domain, }, wantErr: true, }, } for _, cs := range cases { t.Run(cs.name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() data, err := json.Marshal(cs.auth) if err != nil { t.Fatal(err) } _, _, err = s.backendAuthv1(ctx, bytes.NewBuffer(data)) if cs.wantErr && err == nil { t.Fatalf("auth did not err as expected") } if !cs.wantErr && err != nil { t.Fatalf("unexpected auth err: %v", err) } }) } } func TestBackendRouting(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() st := MockStorage() st.AddRoute(domain, user) st.AddToken(token, user, []string{"connect"}) s, err := NewServer(&ServerConfig{ Storage: st, }) if err != nil { t.Fatal(err) } defer s.Close() l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer l.Close() go s.Listen(l, false) cases := []struct { name string wantStatusCode int handler http.HandlerFunc }{ { name: "200 everything's okay", wantStatusCode: http.StatusOK, handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "HTTP 200, everything is okay :)", http.StatusOK) }), }, { name: "500 internal error", wantStatusCode: http.StatusInternalServerError, handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "HTTP 500, the world is on fire", http.StatusInternalServerError) }), }, } for _, cs := range cases { t.Run(cs.name, func(t *testing.T) { ts := httptest.NewServer(cs.handler) defer ts.Close() cc := &ClientConfig{ ConnType: "tcp", ServerAddr: l.Addr().String(), Token: token, BackendURL: ts.URL, Domain: domain, forceTCPClear: true, } c, err := NewClient(cc) if err != nil { t.Fatal(err) } go c.Connect(ctx) // TODO: fix the client library so this ends up actually getting cleaned up time.Sleep(125 * time.Millisecond) req, err := http.NewRequest("GET", "http://cetacean.club/", nil) if err != nil { t.Fatal(err) } resp, err := s.RoundTrip(req) if err != nil { t.Fatalf("error in doing round trip: %v", err) } if cs.wantStatusCode != resp.StatusCode { resp.Write(os.Stdout) t.Fatalf("got status %d instead of %d", resp.StatusCode, cs.wantStatusCode) } }) } } func setupTestServer() (*Server, *mockStorage, net.Listener, error) { st := MockStorage() st.AddRoute(domain, user) st.AddToken(token, user, []string{"connect"}) s, err := NewServer(&ServerConfig{ Storage: st, }) if err != nil { return nil, nil, nil, err } defer s.Close() l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, nil, nil, err } go s.Listen(l, false) return s, st, l, nil } func BenchmarkHTTP200(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() s, _, l, err := setupTestServer() if err != nil { b.Fatal(err) } defer s.Close() defer l.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) defer ts.Close() cc := &ClientConfig{ ConnType: "tcp", ServerAddr: l.Addr().String(), Token: token, BackendURL: ts.URL, Domain: domain, forceTCPClear: true, } c, err := NewClient(cc) if err != nil { b.Fatal(err) } go c.Connect(ctx) // TODO: fix the client library so this ends up actually getting cleaned up for { r := s.GetBackendsForDomain(domain) if len(r) == 0 { time.Sleep(125 * time.Millisecond) continue } break } req, err := http.NewRequest("GET", "http://cetacean.club/", nil) if err != nil { b.Fatal(err) } _, err = s.RoundTrip(req) if err != nil { b.Fatalf("got error on initial request exchange: %v", err) } for n := 0; n < b.N; n++ { resp, err := s.RoundTrip(req) if err != nil { b.Fatalf("got error on %d: %v", n, err) } if resp.StatusCode != http.StatusOK { b.Fail() b.Logf("got %d instead of 200", resp.StatusCode) } } }