/*
 *
 * Copyright 2024 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package delegatingresolver_test

import (
	"context"
	"errors"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"google.golang.org/grpc/internal/envconfig"
	"google.golang.org/grpc/internal/grpctest"
	"google.golang.org/grpc/internal/proxyattributes"
	"google.golang.org/grpc/internal/resolver/delegatingresolver"
	"google.golang.org/grpc/internal/testutils"
	"google.golang.org/grpc/internal/transport/networktype"
	"google.golang.org/grpc/resolver"
	"google.golang.org/grpc/resolver/manual"
	"google.golang.org/grpc/serviceconfig"

	_ "google.golang.org/grpc/resolver/dns" // To register dns resolver.
)

type s struct {
	grpctest.Tester
}

func Test(t *testing.T) {
	grpctest.RunSubTests(t, s{})
}

const (
	defaultTestTimeout      = 10 * time.Second
	defaultTestShortTimeout = 10 * time.Millisecond
)

// createTestResolverClientConn initializes a [testutils.ResolverClientConn] and
// returns it along with channels for resolver state updates and errors.
func createTestResolverClientConn(t *testing.T) (*testutils.ResolverClientConn, chan resolver.State, chan error) {
	t.Helper()
	stateCh := make(chan resolver.State, 1)
	errCh := make(chan error, 1)

	tcc := &testutils.ResolverClientConn{
		Logger:       t,
		UpdateStateF: func(s resolver.State) error { stateCh <- s; return nil },
		ReportErrorF: func(err error) { errCh <- err },
	}
	return tcc, stateCh, errCh
}

// Tests the scenario where no proxy environment variables are set or proxying
// is disabled by the `NO_PROXY` environment variable. The test verifies that
// the delegating resolver creates only a target resolver and that the
// addresses returned by the delegating resolver exactly match those returned
// by the target resolver.
func (s) TestDelegatingResolverNoProxyEnvVarsSet(t *testing.T) {
	hpfe := func(*http.Request) (*url.URL, error) { return nil, nil }
	originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
	delegatingresolver.HTTPSProxyFromEnvironment = hpfe
	defer func() {
		delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
	}()

	const (
		targetTestAddr          = "test.com"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
	)

	// Set up a manual resolver to control the address resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr

	// Create a delegating resolver with no proxy configuration
	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	// Update the manual resolver with a test address.
	targetResolver.UpdateState(resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedTargetTestAddr1},
			{Addr: resolvedTargetTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	// Verify that the delegating resolver outputs the same addresses, as returned
	// by the target resolver.
	wantState := resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedTargetTestAddr1},
			{Addr: resolvedTargetTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	}

	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-time.After(defaultTestTimeout):
		t.Fatal("Timeout when waiting for a state update from the delegating resolver")
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// setupDNS registers a new manual resolver for the DNS scheme, effectively
// overwriting the previously registered DNS resolver. This allows the test to
// mock the DNS resolution for the proxy resolver. It also registers the
// original DNS resolver after the test is done.
func setupDNS(t *testing.T) (*manual.Resolver, chan struct{}) {
	t.Helper()
	mr := manual.NewBuilderWithScheme("dns")

	dnsResolverBuilder := resolver.Get("dns")
	resolver.Register(mr)

	resolverBuilt := make(chan struct{})
	mr.BuildCallback = func(resolver.Target, resolver.ClientConn, resolver.BuildOptions) {
		close(resolverBuilt)
	}

	t.Cleanup(func() { resolver.Register(dnsResolverBuilder) })
	return mr, resolverBuilt
}

func mustBuildResolver(ctx context.Context, t *testing.T, buildCh chan struct{}) {
	t.Helper()
	select {
	case <-buildCh:
	case <-ctx.Done():
		t.Fatalf("Context timed out waiting for resolver to be built.")
	}
}

// proxyAddressWithTargetAttribute creates a resolver.Address for the proxy,
// adding the target address as an attribute.
func proxyAddressWithTargetAttribute(proxyAddr string, targetAddr string) resolver.Address {
	addr := resolver.Address{Addr: proxyAddr}
	addr = proxyattributes.Set(addr, proxyattributes.Options{ConnectAddr: targetAddr})
	return addr
}

func overrideTestHTTPSProxy(t *testing.T, proxyAddr string) {
	t.Helper()
	hpfe := func(*http.Request) (*url.URL, error) {
		return &url.URL{
			Scheme: "https",
			Host:   proxyAddr,
		}, nil
	}
	originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
	delegatingresolver.HTTPSProxyFromEnvironment = hpfe
	t.Cleanup(func() { delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe })
}

// Tests the scenario where proxy is configured and the target URI contains the
// "dns" scheme and target resolution is enabled. The test verifies that the
// addresses returned by the delegating resolver combines the addresses
// returned by the proxy resolver and the target resolver.
func (s) TestDelegatingResolverwithDNSAndProxyWithTargetResolution(t *testing.T) {
	const (
		targetTestAddr          = "test.com"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
		envProxyAddr            = "proxytest.com"
		resolvedProxyTestAddr1  = "11.11.11.11:7687"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("dns")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, true); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedTargetTestAddr1},
			{Addr: resolvedTargetTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{{Addr: resolvedProxyTestAddr1}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	// Verify that the delegating resolver outputs the expected address.
	wantState := resolver.State{
		Addresses: []resolver.Address{
			proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
			proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	}
	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Context timeed out when waiting for a state update from the delegating resolver")
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// Tests the creation of a delegating resolver when a proxy is configured. It
// verifies successful creation for valid targets and ensures the final address
// is from the proxy resolver and contains the original, correctly-formatted
// target address as an attribute.
func (s) TestDelegatingResolverwithDNSAndProxyWithNoTargetResolutionHappyPaths(t *testing.T) {
	const (
		envProxyAddr           = "proxytest.com"
		resolvedProxyTestAddr1 = "11.11.11.11:7687"
	)
	tests := []struct {
		name               string
		target             string
		wantConnectAddress string
		defaultPortEnvVar  bool
	}{
		{
			name:               "no_port ",
			target:             "test.com",
			wantConnectAddress: "test.com:443",
			defaultPortEnvVar:  true,
		},
		{
			name:               "complete_target",
			target:             "test.com:8080",
			wantConnectAddress: "test.com:8080",
			defaultPortEnvVar:  true,
		},
		{
			name:               "no_port_with_default_port_env_variable_disabled",
			target:             "test.com",
			wantConnectAddress: "test.com",
			defaultPortEnvVar:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testutils.SetEnvConfig(t, &envconfig.EnableDefaultPortForProxyTarget, test.defaultPortEnvVar)
			overrideTestHTTPSProxy(t, envProxyAddr)

			targetResolver := manual.NewBuilderWithScheme("dns")
			target := targetResolver.Scheme() + ":///" + test.target
			// Set up a manual DNS resolver to control the proxy address resolution.
			proxyResolver, proxyResolverBuilt := setupDNS(t)

			tcc, stateCh, _ := createTestResolverClientConn(t)
			if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
				t.Fatalf("Delegating resolver creation failed unexpectedly with error: %v", err)
			}
			ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
			defer cancel()

			// Wait for the proxy resolver to be built before calling UpdateState.
			mustBuildResolver(ctx, t, proxyResolverBuilt)
			proxyResolver.UpdateState(resolver.State{
				Addresses: []resolver.Address{
					{Addr: resolvedProxyTestAddr1},
				},
			})

			wantState := resolver.State{
				Addresses: []resolver.Address{proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, test.wantConnectAddress)},
				Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, test.wantConnectAddress)}}},
			}

			var gotState resolver.State
			select {
			case gotState = <-stateCh:
			case <-ctx.Done():
				t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
			}

			if diff := cmp.Diff(gotState, wantState); diff != "" {
				t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
			}
		})
	}
}

// Tests the creation of a delegating resolver when a proxy is configured. It
// verifies correct error handling for invalid targets.
func (s) TestDelegatingResolverwithUnresolvedErrorTargetWithProxy(t *testing.T) {
	const (
		envProxyAddr           = "proxytest.com"
		resolvedProxyTestAddr1 = "11.11.11.11:7687"
	)
	tests := []struct {
		name               string
		target             string
		wantErrorSubstring string
		defaultPortEnvVar  bool
	}{

		{
			name:               "colon_after_host_but_no_port",
			target:             "test.com:",
			wantErrorSubstring: "missing port after port-separator colon",
			defaultPortEnvVar:  true,
		},
		{
			name:               "empty_target",
			target:             "",
			wantErrorSubstring: "missing address",
			defaultPortEnvVar:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testutils.SetEnvConfig(t, &envconfig.EnableDefaultPortForProxyTarget, test.defaultPortEnvVar)
			overrideTestHTTPSProxy(t, envProxyAddr)

			targetResolver := manual.NewBuilderWithScheme("dns")
			target := targetResolver.Scheme() + ":///" + test.target

			tcc, _, _ := createTestResolverClientConn(t)
			_, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
			if err == nil {
				t.Fatalf("Delegating resolver created, want error containing %q", test.wantErrorSubstring)
			}
			if !strings.Contains(err.Error(), test.wantErrorSubstring) {
				t.Fatalf("Delegating resolver failed with error %v, want error containing %v", err.Error(), test.wantErrorSubstring)
			}
		})
	}
}

// Tests the scenario where a proxy is configured, and the target URI scheme is
// not "dns". The test verifies that the addresses returned by the delegating
// resolver include the resolved proxy address and the custom resolved target
// address as attributes of the proxy address.
func (s) TestDelegatingResolverwithCustomResolverAndProxy(t *testing.T) {
	const (
		targetTestAddr          = "test.com"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
		envProxyAddr            = "proxytest.com"
		resolvedProxyTestAddr1  = "11.11.11.11:7687"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedTargetTestAddr1},
			{Addr: resolvedTargetTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{{Addr: resolvedProxyTestAddr1}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	wantState := resolver.State{
		Addresses: []resolver.Address{
			proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
			proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	}
	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// Tests the scenario where a proxy is configured, the target URI scheme is not
// "dns," and both the proxy and target resolvers return endpoints. The test
// verifies that the delegating resolver combines resolved proxy and target
// addresses correctly, returning endpoints with the proxy address populated
// and the target address included as an attribute of the proxy address for
// each combination of proxy and target endpoints.
func (s) TestDelegatingResolverForEndpointsWithProxy(t *testing.T) {
	const (
		targetTestAddr          = "test.com"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
		resolvedTargetTestAddr3 = "3.3.3.3:8080"
		resolvedTargetTestAddr4 = "4.4.4.4:8080"
		envProxyAddr            = "proxytest.com"
		resolvedProxyTestAddr1  = "11.11.11.11:7687"
		resolvedProxyTestAddr2  = "22.22.22.22:7687"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Endpoints: []resolver.Endpoint{
			{
				Addresses: []resolver.Address{
					{Addr: resolvedTargetTestAddr1},
					{Addr: resolvedTargetTestAddr2}},
			},
			{
				Addresses: []resolver.Address{
					{Addr: resolvedTargetTestAddr3},
					{Addr: resolvedTargetTestAddr4}},
			},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})
	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Endpoints: []resolver.Endpoint{
			{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}}},
			{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr2}}},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})
	wantState := resolver.State{
		Endpoints: []resolver.Endpoint{
			{
				Addresses: []resolver.Address{
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr1),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr2),
				},
			},
			{
				Addresses: []resolver.Address{
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr3),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr3),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr4),
					proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr4),
				},
			},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	}
	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Contex timed out when waiting for a state update from the delegating resolver")
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// Tests the scenario where a proxy is configured, the target URI scheme is not
// "dns," and both the proxy and target resolvers return multiple addresses.
// The test verifies that the delegating resolver combines unresolved proxy
// host and target addresses correctly, returning addresses with the proxy host
// populated and the target address included as an attribute.
func (s) TestDelegatingResolverForMultipleProxyAddress(t *testing.T) {
	const (
		targetTestAddr          = "test.com"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
		envProxyAddr            = "proxytest.com"
		resolvedProxyTestAddr1  = "11.11.11.11:7687"
		resolvedProxyTestAddr2  = "22.22.22.22:7687"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)
	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedTargetTestAddr1},
			{Addr: resolvedTargetTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Addresses: []resolver.Address{
			{Addr: resolvedProxyTestAddr1},
			{Addr: resolvedProxyTestAddr2},
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	wantState := resolver.State{
		Addresses: []resolver.Address{
			proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr1),
			proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2),
		},
		ServiceConfig: &serviceconfig.ParseResult{},
	}
	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// Tests that delegatingresolver doesn't panic when the channel closes the
// resolver while it's handling an update from it's child. The test closes the
// delegating resolver, verifies the target resolver is closed and blocks the
// proxy resolver from being closed. The test sends an update from the proxy
// resolver and verifies that the target resolver's ResolveNow method is not
// called after the channels returns an error.
func (s) TestDelegatingResolverUpdateStateDuringClose(t *testing.T) {
	const envProxyAddr = "proxytest.com"

	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	targetResolverCalled := make(chan struct{})
	targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
		close(targetResolverCalled)
	}
	targetResolverCloseCalled := make(chan struct{})
	targetResolver.CloseCallback = func() {
		close(targetResolverCloseCalled)
		t.Log("Target resolver is closed.")
	}

	target := targetResolver.Scheme() + ":///" + "ignored"
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	unblockProxyResolverClose := make(chan struct{}, 1)
	proxyResolver.CloseCallback = func() {
		<-unblockProxyResolverClose
		t.Log("Proxy resolver is closed.")
	}

	tcc, _, _ := createTestResolverClientConn(t)
	tcc.UpdateStateF = func(resolver.State) error {
		return errors.New("test error")
	}
	dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
	if err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
	})

	// Wait for the proxy resolver to be built before calling Close.
	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	// Closing the delegating resolver will block until the test writes to the
	// unblockProxyResolverClose channel.
	go dr.Close()
	select {
	case <-targetResolverCloseCalled:
	case <-ctx.Done():
		t.Fatalf("Context timed out waiting for target resolver's Close method to be called.")
	}

	// Updating the channel will result in an error being returned. Since the
	// target resolver's Close method is already called, the delegating resolver
	// must not call "ResolveNow" on it.
	proxyUpdateCh := make(chan struct{})
	go func() {
		proxyResolver.UpdateState(resolver.State{
			Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
		})
		close(proxyUpdateCh)
	}()
	unblockProxyResolverClose <- struct{}{}

	select {
	case <-targetResolverCalled:
		t.Fatalf("targetResolver.ResolveNow() called unexpectedly.")
	case <-time.After(defaultTestShortTimeout):
	}
	// Wait for the proxy update to complete before returning from the test and
	// before the deferred reassignment of
	// delegatingresolver.HTTPSProxyFromEnvironment. This ensures that we read
	// from the function before it is reassigned, preventing a race condition.
	select {
	case <-proxyUpdateCh:
	case <-ctx.Done():
		t.Fatalf("Context timed out waiting for proxyResolver.UpdateState() to be called.")
	}
}

// Tests that calling cc.UpdateState in a blocking manner from a child resolver
// while handling a ResolveNow call doesn't result in a deadlock. The test uses
// a fake ClientConn that returns an error when calling cc.UpdateState. The test
// makes the proxy resolver update the resolver state. The test verifies that
// the delegating resolver calls ResolveNow on the target resolver when the
// ClientConn returns an error.
func (s) TestDelegatingResolverUpdateStateFromResolveNow(t *testing.T) {
	const envProxyAddr = "proxytest.com"

	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	targetResolverCalled := make(chan struct{})
	targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
		// Updating the resolver state should not deadlock.
		targetResolver.CC().UpdateState(resolver.State{
			Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
		})
		close(targetResolverCalled)
	}

	target := targetResolver.Scheme() + ":///" + "ignored"
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, _, _ := createTestResolverClientConn(t)
	tcc.UpdateStateF = func(resolver.State) error {
		return errors.New("test error")
	}
	_, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
	if err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
	})

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)

	// Updating the channel will result in an error being returned. The
	// delegating resolver should call call "ResolveNow" on the target resolver.
	proxyResolver.UpdateState(resolver.State{
		Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
	})

	select {
	case <-targetResolverCalled:
	case <-ctx.Done():
		t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
	}
}

// Tests that calling cc.UpdateState in a blocking manner from child resolvers
// doesn't result in deadlocks.
func (s) TestDelegatingResolverResolveNow(t *testing.T) {
	const envProxyAddr = "proxytest.com"

	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	targetResolverCalled := make(chan struct{}, 1)
	targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
		// Updating the resolver state should not deadlock.
		targetResolver.CC().UpdateState(resolver.State{
			Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
		})
		targetResolverCalled <- struct{}{}
	}

	target := targetResolver.Scheme() + ":///" + "ignored"
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	proxyResolverCalled := make(chan struct{})
	proxyResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
		// Updating the resolver state should not deadlock.
		proxyResolver.CC().UpdateState(resolver.State{
			Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
		})
		close(proxyResolverCalled)
	}

	tcc, _, _ := createTestResolverClientConn(t)
	dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
	if err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	// ResolveNow of manual proxy resolver will not be called. Proxy resolver is
	// only built when we get the first update from target resolver. Therefore
	// in the first ResolveNow, proxy resolver will be a no-op resolver and only
	// target resolver's ResolveNow will be called.
	dr.ResolveNow(resolver.ResolveNowOptions{})
	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	select {
	case <-targetResolverCalled:
	case <-ctx.Done():
		t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
	}

	mustBuildResolver(ctx, t, proxyResolverBuilt)

	dr.ResolveNow(resolver.ResolveNowOptions{})

	select {
	case <-targetResolverCalled:
	case <-ctx.Done():
		t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
	}
	select {
	case <-proxyResolverCalled:
	case <-ctx.Done():
		t.Fatalf("context timed out waiting for proxyResolver.ResolveNow() to be called.")
	}
}

// Tests the scenario where a proxy is configured, and the resolver returns a
// network type other than tcp for all addresses. The test verifies that the
// delegating resolver avoids the proxy build and directly sends the update
// from target resolver to clientconn.
func (s) TestDelegatingResolverForNonTCPTarget(t *testing.T) {
	const (
		targetTestAddr          = "test.target"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		envProxyAddr            = "proxytest.com"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	_, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	// Set network to anything other than tcp.
	nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix")
	targetResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{nonTCPAddr},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-time.After(defaultTestTimeout):
		t.Fatal("Timeout when waiting for a state update from the delegating resolver")
	}

	// Verify that the delegating resolver doesn't call proxy resolver's
	// UpdateState since we have no tcp address
	select {
	case <-proxyResolverBuilt:
		t.Fatal("Unexpected call to proxy resolver update state")
	case <-time.After(defaultTestShortTimeout):
	}

	wantState := resolver.State{
		Addresses:     []resolver.Address{nonTCPAddr},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	}

	// Verify that the state clientconn receives is same as updated by target
	// resolver, since we want to avoid proxy for any network type apart from
	// tcp.
	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%s", diff)
	}
}

// Tests the scenario where a proxy is configured, and the resolver returns tcp
// and non-tcp addresses. The test verifies that the delegating resolver doesn't
// add proxyatrribute to adresses with network type other than tcp, but adds
// the proxyattribute to addresses with network type tcp.
func (s) TestDelegatingResolverForMixNetworkType(t *testing.T) {
	const (
		targetTestAddr          = "test.target"
		resolvedTargetTestAddr1 = "1.1.1.1:8080"
		resolvedTargetTestAddr2 = "2.2.2.2:8080"
		envProxyAddr            = "proxytest.com"
	)
	overrideTestHTTPSProxy(t, envProxyAddr)

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}
	// Set network to anything other than tcp.
	nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix")
	targetResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{{Addr: envProxyAddr}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
	}
	wantState := resolver.State{
		Addresses:     []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}

// Tests the scenario where a proxy is configured but some addresses are
// excluded (by using the NO_PROXY environment variable). The test verifies that
// the delegating resolver doesn't add proxyatrribute to adresses excluded, but
// adds the proxyattribute to all other addresses.
func (s) TestDelegatingResolverWithNoProxyEnvUsed(t *testing.T) {
	const (
		targetTestAddr            = "test.target"
		noproxyresolvedTargetAddr = "1.1.1.1:8080"
		resolvedTargetTestAddr    = "2.2.2.2:8080"
		envProxyAddr              = "proxytest.com"
	)
	hpfe := func(req *http.Request) (*url.URL, error) {
		// return nil to mimick the scenario where the address is excluded using
		// `NO_PROXY` env variable.
		if req.URL.Host == noproxyresolvedTargetAddr {
			return nil, nil
		}
		return &url.URL{
			Scheme: "https",
			Host:   envProxyAddr,
		}, nil
	}
	originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
	delegatingresolver.HTTPSProxyFromEnvironment = hpfe
	defer func() {
		delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
	}()

	// Manual resolver to control the target resolution.
	targetResolver := manual.NewBuilderWithScheme("test")
	target := targetResolver.Scheme() + ":///" + targetTestAddr
	// Set up a manual DNS resolver to control the proxy address resolution.
	proxyResolver, proxyResolverBuilt := setupDNS(t)

	tcc, stateCh, _ := createTestResolverClientConn(t)
	if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
		t.Fatalf("Failed to create delegating resolver: %v", err)
	}

	targetResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{{Addr: noproxyresolvedTargetAddr}, {Addr: resolvedTargetTestAddr}},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, {Addr: resolvedTargetTestAddr}}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	select {
	case <-stateCh:
		t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
	case <-time.After(defaultTestShortTimeout):
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Wait for the proxy resolver to be built before calling UpdateState.
	mustBuildResolver(ctx, t, proxyResolverBuilt)
	proxyResolver.UpdateState(resolver.State{
		Addresses:     []resolver.Address{{Addr: envProxyAddr}},
		ServiceConfig: &serviceconfig.ParseResult{},
	})

	var gotState resolver.State
	select {
	case gotState = <-stateCh:
	case <-ctx.Done():
		t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
	}
	wantState := resolver.State{
		Addresses:     []resolver.Address{{Addr: noproxyresolvedTargetAddr}, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr)},
		Endpoints:     []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr)}}},
		ServiceConfig: &serviceconfig.ParseResult{},
	}

	if diff := cmp.Diff(gotState, wantState); diff != "" {
		t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
	}
}
