package ptx import ( "context" "errors" "net" "testing" sflib "git.torproject.org/pluggable-transports/snowflake.git/v2/client/lib" "github.com/ooni/probe-cli/v3/internal/atomicx" "github.com/ooni/probe-cli/v3/internal/model/mocks" ) func TestSnowflakeMethodDomainFronting(t *testing.T) { meth := NewSnowflakeRendezvousMethodDomainFronting() if meth.AMPCacheURL() != "" { t.Fatal("invalid amp cache URL") } const brokerURL = "https://snowflake-broker.torproject.net.global.prod.fastly.net/" if meth.BrokerURL() != brokerURL { t.Fatal("invalid broker URL") } const frontDomain = "cdn.sstatic.net" if meth.FrontDomain() != frontDomain { t.Fatal("invalid front domain") } if meth.Name() != "domain_fronting" { t.Fatal("invalid name") } } func TestSnowflakeMethodAMP(t *testing.T) { meth := NewSnowflakeRendezvousMethodAMP() const ampCacheURL = "https://cdn.ampproject.org/" if meth.AMPCacheURL() != ampCacheURL { t.Fatal("invalid amp cache URL") } const brokerURL = "https://snowflake-broker.torproject.net/" if meth.BrokerURL() != brokerURL { t.Fatal("invalid broker URL") } const frontDomain = "www.google.com" if meth.FrontDomain() != frontDomain { t.Fatal("invalid front domain") } if meth.Name() != "amp" { t.Fatal("invalid name") } } func TestNewSnowflakeRendezvousMethod(t *testing.T) { t.Run("for domain_fronted", func(t *testing.T) { meth, err := NewSnowflakeRendezvousMethod("domain_fronting") if err != nil { t.Fatal(err) } if _, ok := meth.(*snowflakeRendezvousMethodDomainFronting); !ok { t.Fatal("unexpected method type") } }) t.Run("for empty string", func(t *testing.T) { meth, err := NewSnowflakeRendezvousMethod("") if err != nil { t.Fatal(err) } if _, ok := meth.(*snowflakeRendezvousMethodDomainFronting); !ok { t.Fatal("unexpected method type") } }) t.Run("for amp", func(t *testing.T) { meth, err := NewSnowflakeRendezvousMethod("amp") if err != nil { t.Fatal(err) } if _, ok := meth.(*snowflakeRendezvousMethodAMP); !ok { t.Fatal("unexpected method type") } }) t.Run("for another value", func(t *testing.T) { meth, err := NewSnowflakeRendezvousMethod("amptani") if !errors.Is(err, ErrSnowflakeNoSuchRendezvousMethod) { t.Fatal("unexpected error", err) } if meth != nil { t.Fatal("unexpected method value") } }) } func TestNewSnowflakeDialer(t *testing.T) { dialer := NewSnowflakeDialer() _, ok := dialer.RendezvousMethod.(*snowflakeRendezvousMethodDomainFronting) if !ok { t.Fatal("invalid rendezvous method type") } } func TestNewSnowflakeDialerWithRendezvousMethod(t *testing.T) { meth := NewSnowflakeRendezvousMethodAMP() dialer := NewSnowflakeDialerWithRendezvousMethod(meth) if meth != dialer.RendezvousMethod { t.Fatal("invalid rendezvous method value") } } func TestSnowflakeDialerWorks(t *testing.T) { // This test may sadly run for a very long time (~10s) if testing.Short() { t.Skip("skip test in short mode") } sfd := NewSnowflakeDialer() conn, err := sfd.DialContext(context.Background()) if err != nil { t.Fatal(err) } if conn == nil { t.Fatal("expected non-nil conn here") } if sfd.Name() != "snowflake" { t.Fatal("the Name function returned an unexpected value") } expect := "snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72" if v := sfd.AsBridgeArgument(); v != expect { t.Fatal("AsBridgeArgument returned an unexpected value", v) } conn.Close() } // mockableSnowflakeTransport is a mock for snowflakeTransport type mockableSnowflakeTransport struct { MockDial func() (net.Conn, error) } // Dial implements snowflakeTransport.Dial. func (txp *mockableSnowflakeTransport) Dial() (net.Conn, error) { return txp.MockDial() } var _ snowflakeTransport = &mockableSnowflakeTransport{} func TestSnowflakeDialerWorksWithMocks(t *testing.T) { sfd := &SnowflakeDialer{ RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(), newClientTransport: func(config sflib.ClientConfig) (snowflakeTransport, error) { return &mockableSnowflakeTransport{ MockDial: func() (net.Conn, error) { return &mocks.Conn{ MockClose: func() error { return nil }, }, nil }, }, nil }, } conn, err := sfd.DialContext(context.Background()) if err != nil { t.Fatal(err) } if conn == nil { t.Fatal("expected non-nil conn here") } if sfd.Name() != "snowflake" { t.Fatal("the Name function returned an unexpected value") } expect := "snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72" if v := sfd.AsBridgeArgument(); v != expect { t.Fatal("AsBridgeArgument returned an unexpected value", v) } conn.Close() } func TestSnowflakeDialerCannotCreateTransport(t *testing.T) { expected := errors.New("mocked error") sfd := &SnowflakeDialer{ RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(), newClientTransport: func(config sflib.ClientConfig) (snowflakeTransport, error) { return nil, expected }, } conn, err := sfd.DialContext(context.Background()) if !errors.Is(err, expected) { t.Fatal("not the error we expected", err) } if conn != nil { t.Fatal("expected nil conn here") } } func TestSnowflakeDialerCannotCreateConnWithNoContextExpiration(t *testing.T) { expected := errors.New("mocked error") sfd := &SnowflakeDialer{ RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(), newClientTransport: func(config sflib.ClientConfig) (snowflakeTransport, error) { return &mockableSnowflakeTransport{ MockDial: func() (net.Conn, error) { return nil, expected }, }, nil }, } conn, err := sfd.DialContext(context.Background()) if !errors.Is(err, expected) { t.Fatal("not the error we expected", err) } if conn != nil { t.Fatal("expected nil conn here") } } func TestSnowflakeDialerCannotCreateConnWithContextExpiration(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() expected := errors.New("mocked error") sfd := &SnowflakeDialer{ RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(), newClientTransport: func(config sflib.ClientConfig) (snowflakeTransport, error) { return &mockableSnowflakeTransport{ MockDial: func() (net.Conn, error) { cancel() // before returning to the caller return nil, expected }, }, nil }, } conn, err := sfd.DialContext(ctx) if !errors.Is(err, context.Canceled) { t.Fatal("not the error we expected", err) } if conn != nil { t.Fatal("expected nil conn here") } } func TestSnowflakeDialerWorksWithWithCancelledContext(t *testing.T) { called := &atomicx.Int64{} ctx, cancel := context.WithCancel(context.Background()) defer cancel() sfd := &SnowflakeDialer{ RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(), newClientTransport: func(config sflib.ClientConfig) (snowflakeTransport, error) { return &mockableSnowflakeTransport{ MockDial: func() (net.Conn, error) { cancel() // cause a cancel before we can really have a conn return &mocks.Conn{ MockClose: func() error { called.Add(1) return nil }, }, nil }, }, nil }, } conn, done, err := sfd.dialContext(ctx) if !errors.Is(err, context.Canceled) { t.Fatal("not the error we expected", err) } if conn != nil { t.Fatal("expected nil conn here") } // synchronize with the end of the inner goroutine <-done if called.Load() != 1 { t.Fatal("the goroutine did not call close") } }