Skip to content

Commit 5a6d05a

Browse files
authored
Merge pull request #108 from Infisical/daniel/proxy-e2e-tests
feat(e2e-tests): infisical proxy coverage
2 parents 8ef3a3c + 2f0519e commit 5a6d05a

8 files changed

Lines changed: 4373 additions & 1406 deletions

File tree

e2e/openapi-cfg.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ output-options:
1919
- createProject
2020
- createKubernetesPamResource
2121
- createRedisPamResource
22+
- deleteSecretV4
23+
- updateSecretV4
24+
- createSecretV4
25+
- getSecretByNameV4
26+
- listSecretsV4
27+

e2e/packages/client/client.gen.go

Lines changed: 3501 additions & 1357 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/packages/infisical/compose.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/docker/compose/v2/pkg/api"
1818
"github.com/docker/docker/api/types/container"
1919
"github.com/docker/docker/api/types/filters"
20+
"github.com/docker/docker/api/types/network"
2021
"github.com/testcontainers/testcontainers-go"
2122
"github.com/testcontainers/testcontainers-go/modules/compose"
2223
"github.com/testcontainers/testcontainers-go/wait"
@@ -144,6 +145,20 @@ func (s *Stack) Down(ctx context.Context) error {
144145
return s.dockerCompose.Down(ctx)
145146
}
146147

148+
// DownWithForce tears down all containers and optionally removes volumes.
149+
// This works even when using container reuse (RunningCompose).
150+
func (s *Stack) DownWithForce(ctx context.Context, removeVolumes bool) error {
151+
if rc, ok := s.dockerCompose.(*RunningCompose); ok {
152+
return rc.DownWithForce(ctx, removeVolumes)
153+
}
154+
// For regular compose stacks, use the standard Down with options
155+
opts := []compose.StackDownOption{compose.RemoveOrphans(true)}
156+
if removeVolumes {
157+
opts = append(opts, compose.RemoveVolumes(true))
158+
}
159+
return s.dockerCompose.Down(ctx, opts...)
160+
}
161+
147162
func (s *Stack) Compose() compose.ComposeStack {
148163
return s.dockerCompose
149164
}
@@ -236,8 +251,8 @@ func WithBackendService(options BackendOptions) StackOption {
236251
Dockerfile: dockerfile,
237252
},
238253
Ports: []types.ServicePortConfig{
239-
{Published: "4000", Target: 4000},
240-
{Published: "9229", Target: 9229},
254+
{Target: 4000}, // Let Docker assign a random host port to avoid conflicts
255+
{Target: 9229},
241256
},
242257
Environment: types.NewMappingWithEquals([]string{
243258
"NODE_ENV=development",
@@ -301,6 +316,59 @@ func (c *RunningCompose) Down(ctx context.Context, opts ...compose.StackDownOpti
301316
return nil
302317
}
303318

319+
// DownWithForce tears down all containers and optionally removes volumes.
320+
// Unlike Down(), this actually removes containers even when using RunningCompose.
321+
func (c *RunningCompose) DownWithForce(ctx context.Context, removeVolumes bool) error {
322+
slog.Info("Force tearing down compose stack", "name", c.name, "removeVolumes", removeVolumes)
323+
324+
containers, err := c.client.ContainerList(ctx, container.ListOptions{
325+
All: true,
326+
Filters: filters.NewArgs(
327+
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, c.name)),
328+
),
329+
})
330+
if err != nil {
331+
return fmt.Errorf("container list: %w", err)
332+
}
333+
334+
// Stop and remove all containers
335+
for _, ctr := range containers {
336+
slog.Info("Stopping and removing container", "id", ctr.ID[:12], "name", ctr.Names)
337+
timeout := 10
338+
if err := c.client.ContainerStop(ctx, ctr.ID, container.StopOptions{Timeout: &timeout}); err != nil {
339+
slog.Warn("Failed to stop container", "id", ctr.ID[:12], "error", err)
340+
}
341+
if err := c.client.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{Force: true, RemoveVolumes: removeVolumes}); err != nil {
342+
slog.Warn("Failed to remove container", "id", ctr.ID[:12], "error", err)
343+
}
344+
}
345+
346+
// Remove the network
347+
networks, err := c.client.NetworkList(ctx, network.ListOptions{
348+
Filters: filters.NewArgs(
349+
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, c.name)),
350+
),
351+
})
352+
if err != nil {
353+
slog.Warn("Failed to list networks", "error", err)
354+
} else {
355+
for _, network := range networks {
356+
slog.Info("Removing network", "name", network.Name)
357+
if err := c.client.NetworkRemove(ctx, network.ID); err != nil {
358+
slog.Warn("Failed to remove network", "name", network.Name, "error", err)
359+
}
360+
}
361+
}
362+
363+
// Clear the cached containers
364+
c.containersLock.Lock()
365+
c.containers = make(map[string]*testcontainers.DockerContainer)
366+
c.containersLock.Unlock()
367+
368+
slog.Info("Compose stack torn down", "name", c.name)
369+
return nil
370+
}
371+
304372
func (c *RunningCompose) Services() []string {
305373
return c.services
306374
}

e2e/proxy/proxy_helpers.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package proxy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"net/http"
8+
"testing"
9+
"time"
10+
11+
"github.com/go-faker/faker/v4"
12+
"github.com/infisical/cli/e2e-tests/packages/client"
13+
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
// ProxyTestHelper provides helper methods for proxy tests
18+
type ProxyTestHelper struct {
19+
T *testing.T
20+
ProxyClient client.ClientWithResponsesInterface // client pointing to proxy
21+
ApiClient client.ClientWithResponsesInterface // client pointing to Infisical directly
22+
ProjectID string
23+
Environment string
24+
}
25+
26+
type Secret struct {
27+
SecretKey string
28+
SecretValue string
29+
}
30+
31+
// NewProxyTestHelper creates a new test helper with clients for both proxy and direct API access
32+
func NewProxyTestHelper(t *testing.T, proxyURL, infisicalURL, identityToken, projectID string) *ProxyTestHelper {
33+
bearerAuth, err := securityprovider.NewSecurityProviderBearerToken(identityToken)
34+
require.NoError(t, err)
35+
36+
// client for requests through the proxy (to test caching)
37+
proxyClient, err := client.NewClientWithResponses(
38+
proxyURL,
39+
client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
40+
client.WithRequestEditorFn(bearerAuth.Intercept),
41+
)
42+
require.NoError(t, err)
43+
44+
// client for direct API access
45+
apiClient, err := client.NewClientWithResponses(
46+
infisicalURL,
47+
client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
48+
client.WithRequestEditorFn(bearerAuth.Intercept),
49+
)
50+
require.NoError(t, err)
51+
52+
return &ProxyTestHelper{
53+
T: t,
54+
ProxyClient: proxyClient,
55+
ApiClient: apiClient,
56+
ProjectID: projectID,
57+
Environment: "dev",
58+
}
59+
}
60+
61+
// CreateSecretWithApi creates a secret directly through Infisical API (not through proxy)
62+
func (h *ProxyTestHelper) CreateSecretWithApi(ctx context.Context, secret Secret) {
63+
secretPath := "/"
64+
resp, err := h.ApiClient.CreateSecretV4WithResponse(ctx, secret.SecretKey, client.CreateSecretV4JSONRequestBody{
65+
ProjectId: h.ProjectID,
66+
Environment: h.Environment,
67+
SecretValue: secret.SecretValue,
68+
SecretPath: &secretPath,
69+
})
70+
require.NoError(h.T, err)
71+
require.Equal(h.T, http.StatusOK, resp.StatusCode(), "Failed to create secret: %s", string(resp.Body))
72+
slog.Info("Created secret", "name", secret.SecretKey, "value", secret.SecretValue)
73+
}
74+
75+
// UpdateSecretWithApi updates a secret directly through Infisical API (not through proxy)
76+
func (h *ProxyTestHelper) UpdateSecretWithApi(ctx context.Context, secret Secret) {
77+
secretPath := "/"
78+
resp, err := h.ApiClient.UpdateSecretV4WithResponse(ctx, secret.SecretKey, client.UpdateSecretV4JSONRequestBody{
79+
ProjectId: h.ProjectID,
80+
Environment: h.Environment,
81+
SecretValue: &secret.SecretValue,
82+
SecretPath: &secretPath,
83+
})
84+
require.NoError(h.T, err)
85+
require.Equal(h.T, http.StatusOK, resp.StatusCode(), "Failed to update secret: %s", string(resp.Body))
86+
slog.Info("Updated secret directly", "name", secret.SecretKey, "newValue", secret.SecretValue)
87+
}
88+
89+
// GetSecretsWithProxy fetches secrets through the proxy
90+
func (h *ProxyTestHelper) GetSecretsWithProxy(ctx context.Context) *client.ListSecretsV4Response {
91+
secretPath := "/"
92+
projectID := h.ProjectID
93+
environment := h.Environment
94+
resp, err := h.ProxyClient.ListSecretsV4WithResponse(ctx, &client.ListSecretsV4Params{
95+
ProjectId: &projectID,
96+
Environment: &environment,
97+
SecretPath: &secretPath,
98+
})
99+
require.NoError(h.T, err)
100+
return resp
101+
}
102+
103+
// GetSecretByNameWithProxy fetches a single secret through the proxy
104+
func (h *ProxyTestHelper) GetSecretByNameWithProxy(ctx context.Context, secretName string) *client.GetSecretByNameV4Response {
105+
secretPath := "/"
106+
environment := h.Environment
107+
resp, err := h.ProxyClient.GetSecretByNameV4WithResponse(ctx, secretName, &client.GetSecretByNameV4Params{
108+
ProjectId: h.ProjectID,
109+
Environment: &environment,
110+
SecretPath: &secretPath,
111+
})
112+
require.NoError(h.T, err)
113+
return resp
114+
}
115+
116+
// UpdateSecretWithProxy updates a secret through the proxy (triggers mutation purging)
117+
func (h *ProxyTestHelper) UpdateSecretWithProxy(ctx context.Context, secret Secret) *client.UpdateSecretV4Response {
118+
secretPath := "/"
119+
resp, err := h.ProxyClient.UpdateSecretV4WithResponse(ctx, secret.SecretKey, client.UpdateSecretV4JSONRequestBody{
120+
ProjectId: h.ProjectID,
121+
Environment: h.Environment,
122+
SecretPath: &secretPath,
123+
SecretValue: &secret.SecretValue,
124+
})
125+
require.NoError(h.T, err)
126+
return resp
127+
}
128+
129+
// DeleteSecretWithProxy deletes a secret through the proxy (triggers mutation purging)
130+
func (h *ProxyTestHelper) DeleteSecretWithProxy(ctx context.Context, secretName string) *client.DeleteSecretV4Response {
131+
secretPath := "/"
132+
resp, err := h.ProxyClient.DeleteSecretV4WithResponse(ctx, secretName, client.DeleteSecretV4JSONRequestBody{
133+
ProjectId: h.ProjectID,
134+
Environment: h.Environment,
135+
SecretPath: &secretPath,
136+
})
137+
require.NoError(h.T, err)
138+
return resp
139+
}
140+
141+
type GenerateSecretOptions struct {
142+
// Prefix is only used if no PresetName is provided
143+
Prefix string
144+
145+
PresetName string
146+
PresetValue string
147+
}
148+
149+
func (h *ProxyTestHelper) GenerateSecret(opts GenerateSecretOptions) Secret {
150+
151+
secretName := ""
152+
secretValue := ""
153+
154+
if opts.PresetName != "" {
155+
secretName = opts.PresetName
156+
}
157+
if opts.PresetValue != "" {
158+
secretValue = opts.PresetValue
159+
}
160+
161+
if secretName == "" {
162+
secretName = fmt.Sprintf("%s%s", opts.Prefix, faker.Word())
163+
}
164+
165+
if secretValue == "" {
166+
secretValue = faker.Password()
167+
}
168+
169+
return Secret{
170+
SecretKey: secretName,
171+
SecretValue: secretValue,
172+
}
173+
174+
}

0 commit comments

Comments
 (0)