Files
coder/codersdk/workspacesdk/workspacesdk_test.go
T
Dean Sheather a1b87a67c6 fix: use client preferred URL for the default DERP (#18911)
The agentsdk currently does a remap of the DERP map to change the
EmbeddedRelay node's URL to match the agent's access URL.

This PR makes changes to the `workspacesdk` (used by clients like the
CLI) and `vpn` (used by Coder Desktop) to match this behavior.

This enables us the ability to try Coder clients in dogfood over a VPN
without changing the global access URL.
2025-07-17 20:17:44 +10:00

148 lines
4.5 KiB
Go

package workspacesdk_test
import (
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"github.com/coder/websocket"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/testutil"
)
func TestWorkspaceRewriteDERPMap(t *testing.T) {
t.Parallel()
// This test ensures that RewriteDERPMap mutates built-in DERPs with the
// client access URL.
dm := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {
EmbeddedRelay: true,
RegionID: 1,
Nodes: []*tailcfg.DERPNode{{
HostName: "bananas.org",
DERPPort: 1,
}},
},
},
}
parsed, err := url.Parse("https://coconuts.org:44558")
require.NoError(t, err)
client := workspacesdk.New(codersdk.New(parsed))
client.RewriteDERPMap(dm)
region := dm.Regions[1]
require.True(t, region.EmbeddedRelay)
require.Len(t, region.Nodes, 1)
node := region.Nodes[0]
require.Equal(t, "coconuts.org", node.HostName)
require.Equal(t, 44558, node.DERPPort)
}
func TestWorkspaceDialerFailure(t *testing.T) {
t.Parallel()
// Setup.
ctx := testutil.Context(t, testutil.WaitShort)
logger := testutil.Logger(t)
// Given: a mock HTTP server which mimicks an unreachable database when calling the coordination endpoint.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
Message: codersdk.DatabaseNotReachable,
Detail: "oops",
})
}))
t.Cleanup(srv.Close)
u, err := url.Parse(srv.URL)
require.NoError(t, err)
// When: calling the coordination endpoint.
dialer := workspacesdk.NewWebsocketDialer(logger, u, &websocket.DialOptions{})
_, err = dialer.Dial(ctx, nil)
// Then: an error indicating a database issue is returned, to conditionalize the behavior of the caller.
require.ErrorIs(t, err, codersdk.ErrDatabaseNotReachable)
}
func TestClient_IsCoderConnectRunning(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitShort)
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/workspaceagents/connection", r.URL.Path)
httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{
HostnameSuffix: "test",
})
}))
defer srv.Close()
apiURL, err := url.Parse(srv.URL)
require.NoError(t, err)
sdkClient := codersdk.New(apiURL)
client := workspacesdk.New(sdkClient)
// Right name, right IP
expectedName := fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, "test")
ctxResolveExpected := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
&fakeResolver{t: t, hostMap: map[string][]net.IP{
expectedName: {net.ParseIP(tsaddr.CoderServiceIPv6().String())},
}})
result, err := client.IsCoderConnectRunning(ctxResolveExpected, workspacesdk.CoderConnectQueryOptions{})
require.NoError(t, err)
require.True(t, result)
// Wrong name
result, err = client.IsCoderConnectRunning(ctxResolveExpected, workspacesdk.CoderConnectQueryOptions{HostnameSuffix: "coder"})
require.NoError(t, err)
require.False(t, result)
// Not found
ctxResolveNotFound := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
&fakeResolver{t: t, err: &net.DNSError{IsNotFound: true}})
result, err = client.IsCoderConnectRunning(ctxResolveNotFound, workspacesdk.CoderConnectQueryOptions{})
require.NoError(t, err)
require.False(t, result)
// Some other error
ctxResolverErr := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
&fakeResolver{t: t, err: xerrors.New("a bad thing happened")})
_, err = client.IsCoderConnectRunning(ctxResolverErr, workspacesdk.CoderConnectQueryOptions{})
require.Error(t, err)
// Right name, wrong IP
ctxResolverWrongIP := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
&fakeResolver{t: t, hostMap: map[string][]net.IP{
expectedName: {net.ParseIP("2001::34")},
}})
result, err = client.IsCoderConnectRunning(ctxResolverWrongIP, workspacesdk.CoderConnectQueryOptions{})
require.NoError(t, err)
require.False(t, result)
}
type fakeResolver struct {
t testing.TB
hostMap map[string][]net.IP
err error
}
func (f *fakeResolver) LookupIP(_ context.Context, network, host string) ([]net.IP, error) {
assert.Equal(f.t, "ip6", network)
return f.hostMap[host], f.err
}