Skip to content

Commit 39f3b2a

Browse files
committed
Break the test into smaller units
1 parent 8480857 commit 39f3b2a

File tree

3 files changed

+350
-340
lines changed

3 files changed

+350
-340
lines changed

e2e/relay/gateway_test.go

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package relay_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log/slog"
8+
"net"
9+
"net/http"
10+
"os"
11+
"path/filepath"
12+
"sync"
13+
"testing"
14+
"time"
15+
16+
"github.com/compose-spec/compose-go/v2/types"
17+
"github.com/google/uuid"
18+
"github.com/infisical/cli/e2e-tests/packages/client"
19+
openapitypes "github.com/oapi-codegen/runtime/types"
20+
"github.com/redis/go-redis/v9"
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
24+
)
25+
26+
func TestRelay_RegistersAGateway(t *testing.T) {
27+
ctx, cancel := context.WithCancel(context.Background())
28+
t.Cleanup(cancel)
29+
30+
infisical := NewInfisicalService().
31+
WithBackendEnvironment(types.NewMappingWithEquals([]string{
32+
// This is needed for the private ip (current host) to be accepted for the relay server
33+
"ALLOW_INTERNAL_IP_CONNECTIONS=true",
34+
})).
35+
Up(t, ctx)
36+
37+
c := infisical.ApiClient()
38+
identity := infisical.CreateMachineIdentity(t, ctx, WithTokenAuth())
39+
require.NotNil(t, identity)
40+
41+
relayName := RandomSlug(2)
42+
relayCmd := Command{
43+
Test: t,
44+
Args: []string{"relay", "start", "--domain", infisical.ApiUrl(t)},
45+
Env: map[string]string{
46+
"INFISICAL_API_URL": infisical.ApiUrl(t),
47+
"INFISICAL_RELAY_NAME": relayName,
48+
"INFISICAL_RELAY_HOST": "host.docker.internal",
49+
"INFISICAL_TOKEN": *identity.TokenAuthToken,
50+
},
51+
}
52+
relayCmd.Start(ctx)
53+
t.Cleanup(relayCmd.Stop)
54+
result := WaitForStderr(t, WaitForStderrOptions{
55+
EnsureCmdRunning: &relayCmd,
56+
ExpectedString: "Relay server started successfully",
57+
})
58+
require.Equal(t, WaitSuccess, result)
59+
60+
tmpLogDir := t.TempDir()
61+
sessionRecordingPath := filepath.Join(tmpLogDir, "session-recording")
62+
require.NoError(t, os.MkdirAll(sessionRecordingPath, 0755))
63+
gatewayName := RandomSlug(2)
64+
gatewayCmd := Command{
65+
Test: t,
66+
Args: []string{"gateway", "start",
67+
fmt.Sprintf("--name=%s", gatewayName),
68+
fmt.Sprintf("--pam-session-recording-path=%s", sessionRecordingPath),
69+
},
70+
Env: map[string]string{
71+
"INFISICAL_API_URL": infisical.ApiUrl(t),
72+
"INFISICAL_TOKEN": *identity.TokenAuthToken,
73+
},
74+
}
75+
gatewayCmd.Start(ctx)
76+
t.Cleanup(gatewayCmd.Stop)
77+
78+
result = WaitForStderr(t, WaitForStderrOptions{
79+
EnsureCmdRunning: &gatewayCmd,
80+
ExpectedString: "Successfully registered gateway and received certificates",
81+
})
82+
require.Equal(t, WaitSuccess, result)
83+
84+
result = WaitFor(t, WaitForOptions{
85+
EnsureCmdRunning: &gatewayCmd,
86+
Condition: func() ConditionResult {
87+
resp, err := c.ListGatewaysWithResponse(ctx)
88+
if err != nil {
89+
return ConditionWait
90+
}
91+
if resp.StatusCode() != http.StatusOK {
92+
return ConditionWait
93+
}
94+
for _, gateway := range *resp.JSON200 {
95+
slog.Info(
96+
"Gateway info",
97+
"id", gateway.Id,
98+
"name", gateway.Name,
99+
"identityId", gateway.IdentityId,
100+
"heartbeat", gateway.Heartbeat,
101+
)
102+
if gateway.Name == gatewayName && gateway.Heartbeat != nil {
103+
slog.Info("Confirmed gateway heartbeat")
104+
return ConditionSuccess
105+
}
106+
}
107+
return ConditionWait
108+
},
109+
})
110+
require.Equal(t, WaitSuccess, result)
111+
112+
result = WaitForStderr(t, WaitForStderrOptions{
113+
EnsureCmdRunning: &gatewayCmd,
114+
ExpectedString: "Gateway is reachable by Infisical",
115+
})
116+
assert.Equal(t, WaitSuccess, result)
117+
}
118+
119+
func TestRelay_RelayGatewayConnectivity(t *testing.T) {
120+
ctx, cancel := context.WithCancel(context.Background())
121+
t.Cleanup(cancel)
122+
123+
infisical := NewInfisicalService().
124+
WithBackendEnvironment(types.NewMappingWithEquals([]string{
125+
// This is needed for the private ip (current host) to be accepted for the relay server
126+
"ALLOW_INTERNAL_IP_CONNECTIONS=true",
127+
})).
128+
Up(t, ctx)
129+
130+
identity := infisical.CreateMachineIdentity(t, ctx, WithTokenAuth())
131+
require.NotNil(t, identity)
132+
133+
relayName := RandomSlug(2)
134+
relayCmd := Command{
135+
Test: t,
136+
Args: []string{"relay", "start", "--domain", infisical.ApiUrl(t)},
137+
Env: map[string]string{
138+
"INFISICAL_API_URL": infisical.ApiUrl(t),
139+
"INFISICAL_RELAY_NAME": relayName,
140+
"INFISICAL_RELAY_HOST": "host.docker.internal",
141+
"INFISICAL_TOKEN": *identity.TokenAuthToken,
142+
},
143+
}
144+
relayCmd.Start(ctx)
145+
t.Cleanup(relayCmd.Stop)
146+
result := WaitForStderr(t, WaitForStderrOptions{
147+
EnsureCmdRunning: &relayCmd,
148+
ExpectedString: "Relay server started successfully",
149+
})
150+
require.Equal(t, WaitSuccess, result)
151+
152+
tmpLogDir := t.TempDir()
153+
sessionRecordingPath := filepath.Join(tmpLogDir, "session-recording")
154+
require.NoError(t, os.MkdirAll(sessionRecordingPath, 0755))
155+
gatewayName := RandomSlug(2)
156+
gatewayCmd := Command{
157+
Test: t,
158+
Args: []string{"gateway", "start",
159+
fmt.Sprintf("--name=%s", gatewayName),
160+
fmt.Sprintf("--pam-session-recording-path=%s", sessionRecordingPath),
161+
},
162+
Env: map[string]string{
163+
"INFISICAL_API_URL": infisical.ApiUrl(t),
164+
"INFISICAL_TOKEN": *identity.TokenAuthToken,
165+
},
166+
}
167+
gatewayCmd.Start(ctx)
168+
t.Cleanup(gatewayCmd.Stop)
169+
result = WaitForStderr(t, WaitForStderrOptions{
170+
EnsureCmdRunning: &gatewayCmd,
171+
ExpectedString: "Gateway is reachable by Infisical",
172+
})
173+
assert.Equal(t, WaitSuccess, result)
174+
175+
c := infisical.ApiClient()
176+
var gatewayId openapitypes.UUID
177+
resp, err := c.ListGatewaysWithResponse(ctx)
178+
require.NoError(t, err)
179+
require.Equal(t, http.StatusOK, resp.StatusCode())
180+
for _, gateway := range *resp.JSON200 {
181+
slog.Info(
182+
"Gateway info",
183+
"id", gateway.Id,
184+
"name", gateway.Name,
185+
"identityId", gateway.IdentityId,
186+
"heartbeat", gateway.Heartbeat,
187+
)
188+
if gateway.Name == gatewayName && gateway.Heartbeat != nil {
189+
gatewayId = gateway.Id
190+
slog.Info("Found gateway ID", "gatewayId", gatewayId)
191+
break
192+
}
193+
}
194+
require.NotZero(t, gatewayId, "Gateway ID should be set")
195+
196+
projDesc := "e2e tests for PAM connectivity"
197+
template := "default"
198+
projectType := client.Pam
199+
projectResp, err := c.CreateProjectWithResponse(ctx, client.CreateProjectJSONRequestBody{
200+
ProjectName: "pam-tests",
201+
ProjectDescription: &projDesc,
202+
Template: &template,
203+
Type: &projectType,
204+
})
205+
require.NoError(t, err)
206+
require.Equal(t, projectResp.StatusCode(), http.StatusOK)
207+
projectId := projectResp.JSON200.Project.Id
208+
209+
t.Run("kubernetes", func(t *testing.T) {
210+
t.Parallel()
211+
ctx := t.Context()
212+
// Create a mock HTTP server running on a random port in a goroutine
213+
// The HTTP server implements a mock /version endpoint that returns dummy data
214+
// and marks a variable as true when the endpoint is hit
215+
var versionEndpointHit bool
216+
var versionEndpointHitMu sync.Mutex
217+
218+
// Create a listener on a random port (port 0 means OS assigns an available port)
219+
listener, err := net.Listen("tcp", ":0")
220+
require.NoError(t, err)
221+
222+
server := &http.Server{
223+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
224+
if r.URL.Path == "/version" {
225+
versionEndpointHitMu.Lock()
226+
versionEndpointHit = true
227+
versionEndpointHitMu.Unlock()
228+
229+
w.Header().Set("Content-Type", "application/json")
230+
w.WriteHeader(http.StatusOK)
231+
// Return dummy version data
232+
versionData := map[string]interface{}{
233+
"version": "1.0.0",
234+
"build": "test-build",
235+
}
236+
json.NewEncoder(w).Encode(versionData)
237+
} else {
238+
w.WriteHeader(http.StatusNotFound)
239+
}
240+
}),
241+
}
242+
243+
// Start the server in a goroutine
244+
go func() {
245+
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
246+
t.Errorf("Mock HTTP server error: %v", err)
247+
}
248+
}()
249+
250+
// Clean up the server when the test completes
251+
t.Cleanup(func() {
252+
shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 5*time.Second)
253+
defer shutdownCancel()
254+
server.Shutdown(shutdownCtx)
255+
})
256+
257+
// Get the server URL
258+
serverURL := fmt.Sprintf("http://%s", listener.Addr().String())
259+
slog.Info("Mock HTTP server started", "url", serverURL)
260+
261+
k8sPamResResp, err := c.CreateKubernetesPamResourceWithResponse(
262+
ctx,
263+
client.CreateKubernetesPamResourceJSONRequestBody{
264+
ProjectId: uuid.MustParse(projectId),
265+
GatewayId: gatewayId,
266+
Name: "k8s-resource",
267+
ConnectionDetails: struct {
268+
SslCertificate *string `json:"sslCertificate,omitempty"`
269+
SslRejectUnauthorized bool `json:"sslRejectUnauthorized"`
270+
Url string `json:"url"`
271+
}{
272+
Url: serverURL,
273+
SslRejectUnauthorized: false,
274+
},
275+
})
276+
require.NoError(t, err)
277+
require.Equal(t, k8sPamResResp.StatusCode(), http.StatusOK)
278+
require.True(t, versionEndpointHit)
279+
})
280+
281+
t.Run("redis", func(t *testing.T) {
282+
t.Parallel()
283+
ctx := t.Context()
284+
// Start a Redis container using testcontainers Redis module
285+
redisContainer, err := tcredis.Run(ctx, "redis:8.4.0")
286+
require.NoError(t, err)
287+
t.Cleanup(func() {
288+
err := redisContainer.Terminate(ctx)
289+
if err != nil {
290+
t.Logf("Failed to terminate Redis container: %v", err)
291+
}
292+
})
293+
294+
// Get the Redis connection string
295+
connectionString, err := redisContainer.ConnectionString(ctx)
296+
require.NoError(t, err)
297+
slog.Info("Redis connection string", "connectionString", connectionString)
298+
299+
// Parse connection string to get host and port for PAM resource
300+
redisHost, err := redisContainer.Host(ctx)
301+
require.NoError(t, err)
302+
redisPort, err := redisContainer.MappedPort(ctx, "6379")
303+
require.NoError(t, err)
304+
305+
// Verify Redis is accessible by connecting to it
306+
opt, err := redis.ParseURL(connectionString)
307+
require.NoError(t, err)
308+
rdb := redis.NewClient(opt)
309+
t.Cleanup(func() { rdb.Close() })
310+
311+
// Test connection to Redis
312+
pong, err := rdb.Ping(ctx).Result()
313+
require.NoError(t, err)
314+
require.Equal(t, "PONG", pong)
315+
slog.Info("Verified Redis is accessible", "addr", connectionString)
316+
317+
// Create Redis PAM resource
318+
redisPortFloat := float32(redisPort.Int())
319+
redisPamResResp, err := c.CreateRedisPamResourceWithResponse(
320+
ctx,
321+
client.CreateRedisPamResourceJSONRequestBody{
322+
ProjectId: uuid.MustParse(projectId),
323+
GatewayId: gatewayId,
324+
Name: "redis-resource",
325+
ConnectionDetails: struct {
326+
Host string `json:"host"`
327+
Port float32 `json:"port"`
328+
SslCertificate *string `json:"sslCertificate,omitempty"`
329+
SslEnabled bool `json:"sslEnabled"`
330+
SslRejectUnauthorized bool `json:"sslRejectUnauthorized"`
331+
}{
332+
Host: redisHost,
333+
Port: redisPortFloat,
334+
SslEnabled: false,
335+
SslRejectUnauthorized: false,
336+
},
337+
})
338+
require.NoError(t, err)
339+
require.Equal(t, redisPamResResp.StatusCode(), http.StatusOK)
340+
slog.Info("Redis PAM resource created successfully")
341+
})
342+
}

e2e/relay/helpers_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,11 @@ func WaitForStderr(t *testing.T, opts WaitForStderrOptions) WaitResult {
592592
}
593593
return WaitFor(t, waitOpts)
594594
}
595+
596+
func RandomSlug(numWords int) string {
597+
var words []string
598+
for i := 0; i < numWords; i++ {
599+
words = append(words, strings.ToLower(faker.Word()))
600+
}
601+
return strings.Join(words, "-")
602+
}

0 commit comments

Comments
 (0)