diff --git a/packages/api/internal/handlers/sandbox_get.go b/packages/api/internal/handlers/sandbox_get.go index 7e6e3b40cf..6687e10bf7 100644 --- a/packages/api/internal/handlers/sandbox_get.go +++ b/packages/api/internal/handlers/sandbox_get.go @@ -264,8 +264,6 @@ func (a *APIStore) GetSandboxesSandboxID(c *gin.Context, id string) { networkConfig = lastSnapshot.Snapshot.Config.Network } - pausedAlias := firstAlias(lastSnapshot.Aliases) - sandbox := api.SandboxDetail{ ClientID: consts.ClientID, // for backwards compatibility we need to return a client id TemplateID: lastSnapshot.Snapshot.BaseEnvID, @@ -285,7 +283,13 @@ func (a *APIStore) GetSandboxesSandboxID(c *gin.Context, id string) { PauseMode: sandboxPauseModeToAPI(lastSnapshot.Snapshot.Config), } - sandbox.Alias = &pausedAlias + if len(lastSnapshot.Aliases) > 0 { + sandbox.Alias = &lastSnapshot.Aliases[0] + } + + if lastSnapshot.Snapshot.Config != nil { + sandbox.VolumeMounts = convertFromDBMountsToAPIMounts(lastSnapshot.Snapshot.Config.VolumeMounts) + } if lastSnapshot.Snapshot.Metadata != nil { metadata := api.SandboxMetadata(lastSnapshot.Snapshot.Metadata) diff --git a/packages/api/internal/handlers/sandbox_get_test.go b/packages/api/internal/handlers/sandbox_get_test.go index 8f4cafeb18..2426272c82 100644 --- a/packages/api/internal/handlers/sandbox_get_test.go +++ b/packages/api/internal/handlers/sandbox_get_test.go @@ -1,16 +1,113 @@ package handlers import ( + "context" "encoding/json" + "net/http" + "net/http/httptest" "testing" + "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/launchdarkly/go-server-sdk/v7/testhelpers/ldtestdata" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/metric/noop" + "google.golang.org/grpc/credentials/insecure" "github.com/e2b-dev/infra/packages/api/internal/api" + snapshotcache "github.com/e2b-dev/infra/packages/api/internal/cache/snapshots" + "github.com/e2b-dev/infra/packages/api/internal/cfg" + "github.com/e2b-dev/infra/packages/api/internal/clusters" + "github.com/e2b-dev/infra/packages/api/internal/orchestrator" + "github.com/e2b-dev/infra/packages/auth/pkg/auth" + authtypes "github.com/e2b-dev/infra/packages/auth/pkg/types" + authqueries "github.com/e2b-dev/infra/packages/db/pkg/auth/queries" dbtypes "github.com/e2b-dev/infra/packages/db/pkg/types" + "github.com/e2b-dev/infra/packages/db/queries" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" + redis_utils "github.com/e2b-dev/infra/packages/shared/pkg/redis" + "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" ) +func TestGetSandboxesSandboxID_PausedMetadata(t *testing.T) { + t.Parallel() + + redisClient := redis_utils.SetupContainerInstance(t) + flags, err := featureflags.NewClientWithDatasource(ldtestdata.DataSource()) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, flags.Close(context.Background())) }) + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + snapshots := snapshotcache.NewSnapshotCache(nil, redisClient) + orch, err := orchestrator.New(ctx, cfg.Config{}, &telemetry.Client{MeterProvider: noop.NewMeterProvider()}, + nil, true, nil, redisClient, nil, clusters.NewTestPool(), flags, nil, snapshots, nil, + insecure.NewCredentials(), insecure.NewCredentials()) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, orch.Close(context.Background())) + require.NoError(t, snapshots.Close(context.Background())) + }) + store := &APIStore{orchestrator: orch, snapshotCache: snapshots} + teamID := uuid.New() + + for _, tt := range []struct { + name string + config *dbtypes.PausedSandboxConfig + aliases []string + wantMounts string + }{ + { + name: "mounts without alias", + config: &dbtypes.PausedSandboxConfig{VolumeMounts: []*dbtypes.SandboxVolumeMountConfig{ + {ID: "vol-1", Type: "persistent", Name: "data", Path: "/mnt/data"}, + {ID: "vol-2", Type: "persistent", Name: "models", Path: "/opt/models"}, + }}, + wantMounts: `[{"name":"data","path":"/mnt/data"},{"name":"models","path":"/opt/models"}]`, + }, + {name: "empty mounts with aliases", config: &dbtypes.PausedSandboxConfig{}, aliases: []string{"first", "second"}, wantMounts: `[]`}, + {name: "legacy snapshot without config"}, + } { + t.Run(tt.name, func(t *testing.T) { + const sandboxID = "pausedsandbox" + diskSize := int64(1024) + envdVersion := "v1.0.0" + data, err := json.Marshal(snapshotcache.SnapshotInfo{ + Aliases: tt.aliases, + Snapshot: queries.Snapshot{ + SandboxID: sandboxID, TeamID: teamID, BaseEnvID: "template", Config: tt.config, + }, + EnvBuild: queries.EnvBuild{TotalDiskSizeMb: &diskSize, EnvdVersion: &envdVersion}, + }) + require.NoError(t, err) + require.NoError(t, redisClient.Set(t.Context(), "snapshot:last:"+sandboxID, data, 5*time.Minute).Err()) + + recorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(recorder) + ginCtx.Request = httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/sandboxes/"+sandboxID, nil) + auth.SetTeamInfoForTest(t, ginCtx, &authtypes.Team{Team: &authqueries.Team{ID: teamID}}) + store.GetSandboxesSandboxID(ginCtx, sandboxID) + + require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String()) + var body map[string]json.RawMessage + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &body)) + assert.JSONEq(t, `"paused"`, string(body["state"])) + if tt.wantMounts == "" { + assert.NotContains(t, body, "volumeMounts") + } else { + assert.JSONEq(t, tt.wantMounts, string(body["volumeMounts"])) + } + if len(tt.aliases) == 0 { + assert.NotContains(t, body, "alias") + } else { + assert.JSONEq(t, `"first"`, string(body["alias"])) + } + }) + } +} + func TestSandboxPauseModeToAPI(t *testing.T) { t.Parallel()