Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 02ac021

Browse files
authored
chore/enterpriseportal: manual E2E test suite (#64057)
Adds a super simple E2E test suite that must be run with `sg test enterprise-portal-e2e` against a locally running Enterprise Portal instance. This is not intended to be filled with super granular assertions - it simply tests that everything is wired up correctly and runs end-to-end. Caught at ~3 issues with this already, amended various downstack PRs with the fix 😆 Closes https://linear.app/sourcegraph/issue/CORE-229/enterprise-portal-basic-manual-e2e-testing-setup ## Test plan ``` sg start dotcom sg test enterprise-portal-e2e ``` No additional configuration required, the defaults work as-is
1 parent 0825358 commit 02ac021

File tree

4 files changed

+318
-0
lines changed

4 files changed

+318
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
load("//dev:go_defs.bzl", "go_test")
2+
3+
go_test(
4+
name = "e2e_test",
5+
srcs = ["e2e_test.go"],
6+
deps = [
7+
"//internal/grpc/defaults",
8+
"//internal/grpc/grpcoauth",
9+
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
10+
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
11+
"@com_github_sourcegraph_log//logtest",
12+
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
13+
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
14+
"@com_github_stretchr_testify//assert",
15+
"@com_github_stretchr_testify//require",
16+
"@org_golang_google_grpc//:go_default_library",
17+
"@org_golang_google_protobuf//encoding/protojson",
18+
"@org_golang_google_protobuf//proto",
19+
"@org_golang_google_protobuf//types/known/fieldmaskpb",
20+
"@org_golang_google_protobuf//types/known/timestamppb",
21+
],
22+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Enterprise Portal E2E tests
2+
3+
This are curreently manually run only, and intended for use in development to sanity-check the end-to-end implementation of Enterprise Portal RPCs.
4+
5+
```sh
6+
sg start dotcom
7+
sg test enterprise-portal-e2e
8+
```
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package qa
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
"os"
8+
"testing"
9+
"time"
10+
11+
"google.golang.org/grpc"
12+
"google.golang.org/protobuf/encoding/protojson"
13+
"google.golang.org/protobuf/proto"
14+
"google.golang.org/protobuf/types/known/fieldmaskpb"
15+
"google.golang.org/protobuf/types/known/timestamppb"
16+
17+
"github.com/sourcegraph/log/logtest"
18+
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
20+
21+
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
22+
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
23+
"github.com/sourcegraph/sourcegraph/internal/grpc/defaults"
24+
"github.com/sourcegraph/sourcegraph/internal/grpc/grpcoauth"
25+
26+
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
27+
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
28+
)
29+
30+
type Clients struct {
31+
Subscriptions subscriptionsv1.SubscriptionsServiceClient
32+
CodyAccess codyaccessv1.CodyAccessServiceClient
33+
}
34+
35+
func newE2EClients(t *testing.T) *Clients {
36+
os.Setenv("INSECURE_DEV", "true")
37+
38+
server, ok := os.LookupEnv("EP_E2E_ENTERPRISEPORTAL_SERVER")
39+
if !ok || server == "" {
40+
t.Skip("E2E_GATEWAY_ENDPOINT must be set, skipping")
41+
}
42+
addr, err := url.Parse(server)
43+
require.NoError(t, err)
44+
if addr.Hostname() != "127.0.0.1" && addr.Hostname() != "localhost" { // CI:LOCALHOST_OK
45+
// For now, prevent us from running the test against a live instance
46+
t.Error("EP_E2E_ENTERPRISEPORTAL_SERVER must not be a live Enterprise Portal instance")
47+
t.FailNow()
48+
}
49+
50+
samsServer := os.Getenv("EP_E2E_SAMS_SERVER")
51+
clientID := os.Getenv("EP_E2E_SAMS_CLIENT_ID")
52+
clientSecret := os.Getenv("EP_E2E_SAMS_CLIENT_SECRET")
53+
if samsServer == "" || clientID == "" || clientSecret == "" {
54+
t.Error("EP_E2E_SAMS_SERVER, EP_E2E_SAMS_CLIENT_ID, and EP_E2E_SAMS_CLIENT_SECRET must be set")
55+
t.FailNow()
56+
}
57+
58+
t.Logf(`== Enterprise Portal E2E testing
59+
- Enterprise Portal: %s
60+
- SAMS server: %s
61+
- Client ID: %s`, server, samsServer, clientID)
62+
63+
ts := sams.ClientCredentialsTokenSource(
64+
sams.ConnConfig{ExternalURL: samsServer},
65+
clientID,
66+
clientSecret,
67+
[]scopes.Scope{
68+
scopes.ToScope(scopes.ServiceEnterprisePortal, "codyaccess", scopes.ActionRead),
69+
scopes.ToScope(scopes.ServiceEnterprisePortal, "codyaccess", scopes.ActionWrite),
70+
scopes.ToScope(scopes.ServiceEnterprisePortal, "subscription", scopes.ActionRead),
71+
scopes.ToScope(scopes.ServiceEnterprisePortal, "subscription", scopes.ActionWrite),
72+
},
73+
)
74+
_, err = ts.Token()
75+
require.NoError(t, err)
76+
creds := grpc.WithPerRPCCredentials(grpcoauth.TokenSource{TokenSource: ts})
77+
78+
client, err := grpc.NewClient("dns:///"+addr.Host,
79+
defaults.DialOptions(logtest.Scoped(t).Scoped("grpc"), creds)...)
80+
require.NoError(t, err)
81+
t.Cleanup(func() { _ = client.Close() })
82+
83+
return &Clients{
84+
Subscriptions: subscriptionsv1.NewSubscriptionsServiceClient(client),
85+
CodyAccess: codyaccessv1.NewCodyAccessServiceClient(client),
86+
}
87+
}
88+
89+
func prettyPrint(t *testing.T, m proto.Message) {
90+
data, err := protojson.MarshalOptions{Multiline: true}.Marshal(m)
91+
require.NoError(t, err)
92+
t.Log(string(data))
93+
}
94+
95+
func TestEnterprisePortal(t *testing.T) {
96+
clients := newE2EClients(t)
97+
98+
ctx := context.Background()
99+
if deadline, ok := t.Deadline(); ok {
100+
var cancel func()
101+
ctx, cancel = context.WithDeadline(ctx, deadline)
102+
t.Cleanup(cancel)
103+
}
104+
105+
// Run several in parallel, for a more realistic emulation.
106+
runID := time.Now().UnixMilli()
107+
for idx := 0; idx < 3; idx += 1 {
108+
t.Run(fmt.Sprintf("Run %d", idx), func(t *testing.T) {
109+
t.Parallel()
110+
111+
runLifecycleTest(t, ctx, clients, fmt.Sprintf("%d-%d", runID, idx))
112+
})
113+
}
114+
}
115+
116+
func runLifecycleTest(t *testing.T, ctx context.Context, clients *Clients, runID string) {
117+
subscriptionName := fmt.Sprintf("E2E test %s", runID)
118+
subscriptionDomain := fmt.Sprintf("%s.e2etest.org", runID)
119+
120+
var createdSubscriptionID string
121+
t.Run("Create subscription", func(t *testing.T) {
122+
got, err := clients.Subscriptions.CreateEnterpriseSubscription(
123+
ctx,
124+
&subscriptionsv1.CreateEnterpriseSubscriptionRequest{
125+
Subscription: &subscriptionsv1.EnterpriseSubscription{
126+
DisplayName: subscriptionName,
127+
InstanceDomain: subscriptionDomain,
128+
InstanceType: subscriptionsv1.EnterpriseSubscriptionInstanceType_ENTERPRISE_SUBSCRIPTION_INSTANCE_TYPE_INTERNAL,
129+
},
130+
Message: "E2E test",
131+
},
132+
)
133+
require.NoError(t, err)
134+
createdSubscriptionID = got.GetSubscription().GetId()
135+
prettyPrint(t, got)
136+
})
137+
138+
t.Run("Update subscription with domain", func(t *testing.T) {
139+
got, err := clients.Subscriptions.UpdateEnterpriseSubscription(ctx, &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
140+
Subscription: &subscriptionsv1.EnterpriseSubscription{
141+
Id: createdSubscriptionID,
142+
InstanceDomain: subscriptionDomain,
143+
},
144+
UpdateMask: &fieldmaskpb.FieldMask{
145+
Paths: []string{"instance_domain"},
146+
},
147+
})
148+
require.NoError(t, err)
149+
prettyPrint(t, got)
150+
})
151+
152+
t.Run("Get subscription", func(t *testing.T) {
153+
got, err := clients.Subscriptions.GetEnterpriseSubscription(ctx, &subscriptionsv1.GetEnterpriseSubscriptionRequest{
154+
Query: &subscriptionsv1.GetEnterpriseSubscriptionRequest_Id{
155+
Id: createdSubscriptionID,
156+
},
157+
})
158+
require.NoError(t, err)
159+
prettyPrint(t, got)
160+
})
161+
162+
var createdLicenseID string
163+
t.Run("Create license", func(t *testing.T) {
164+
got, err := clients.Subscriptions.CreateEnterpriseSubscriptionLicense(ctx, &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
165+
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
166+
SubscriptionId: createdSubscriptionID,
167+
License: &subscriptionsv1.EnterpriseSubscriptionLicense_Key{
168+
Key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
169+
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
170+
Tags: []string{"dev", "e2e"},
171+
UserCount: 123,
172+
ExpireTime: timestamppb.New(time.Now().Add(time.Hour)),
173+
},
174+
},
175+
},
176+
},
177+
Message: "E2E test",
178+
})
179+
require.NoError(t, err)
180+
createdLicenseID = got.GetLicense().GetId()
181+
prettyPrint(t, got)
182+
})
183+
184+
t.Run("Get license", func(t *testing.T) {
185+
got, err := clients.Subscriptions.ListEnterpriseSubscriptionLicenses(ctx, &subscriptionsv1.ListEnterpriseSubscriptionLicensesRequest{
186+
Filters: []*subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter{
187+
{
188+
Filter: &subscriptionsv1.ListEnterpriseSubscriptionLicensesFilter_SubscriptionId{
189+
SubscriptionId: createdSubscriptionID,
190+
},
191+
},
192+
},
193+
})
194+
require.NoError(t, err)
195+
assert.NotEmpty(t, got.GetLicenses())
196+
assert.Equal(t, createdLicenseID, got.GetLicenses()[0].GetId())
197+
prettyPrint(t, got)
198+
})
199+
200+
t.Run("Get Cody Gateway access", func(t *testing.T) {
201+
got, err := clients.CodyAccess.GetCodyGatewayAccess(ctx, &codyaccessv1.GetCodyGatewayAccessRequest{
202+
Query: &codyaccessv1.GetCodyGatewayAccessRequest_SubscriptionId{
203+
SubscriptionId: createdSubscriptionID,
204+
},
205+
})
206+
require.NoError(t, err)
207+
assert.False(t, got.GetAccess().GetEnabled(),
208+
"newly created subscription should be disabled")
209+
prettyPrint(t, got)
210+
})
211+
212+
t.Run("Update Cody Gateway access", func(t *testing.T) {
213+
got, err := clients.CodyAccess.UpdateCodyGatewayAccess(ctx, &codyaccessv1.UpdateCodyGatewayAccessRequest{
214+
Access: &codyaccessv1.CodyGatewayAccess{
215+
SubscriptionId: createdSubscriptionID,
216+
Enabled: true,
217+
},
218+
UpdateMask: &fieldmaskpb.FieldMask{
219+
Paths: []string{"enabled"},
220+
},
221+
})
222+
require.NoError(t, err)
223+
assert.True(t, got.GetAccess().GetEnabled())
224+
prettyPrint(t, got)
225+
})
226+
227+
t.Run("Revoke license", func(t *testing.T) {
228+
got, err := clients.Subscriptions.RevokeEnterpriseSubscriptionLicense(ctx, &subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest{
229+
LicenseId: createdLicenseID,
230+
})
231+
require.NoError(t, err)
232+
prettyPrint(t, got)
233+
234+
t.Run("Get Cody Gateway access", func(t *testing.T) {
235+
got, err := clients.CodyAccess.GetCodyGatewayAccess(ctx, &codyaccessv1.GetCodyGatewayAccessRequest{
236+
Query: &codyaccessv1.GetCodyGatewayAccessRequest_SubscriptionId{
237+
SubscriptionId: createdSubscriptionID,
238+
},
239+
})
240+
require.NoError(t, err)
241+
prettyPrint(t, got)
242+
})
243+
})
244+
245+
t.Run("Archive subscription", func(t *testing.T) {
246+
got, err := clients.Subscriptions.ArchiveEnterpriseSubscription(ctx, &subscriptionsv1.ArchiveEnterpriseSubscriptionRequest{
247+
SubscriptionId: createdSubscriptionID,
248+
})
249+
require.NoError(t, err)
250+
prettyPrint(t, got)
251+
252+
t.Run("Get Cody Gateway access", func(t *testing.T) {
253+
_, err := clients.CodyAccess.GetCodyGatewayAccess(ctx, &codyaccessv1.GetCodyGatewayAccessRequest{
254+
Query: &codyaccessv1.GetCodyGatewayAccessRequest_SubscriptionId{
255+
SubscriptionId: createdSubscriptionID,
256+
},
257+
})
258+
require.Error(t, err)
259+
t.Logf("Got expected error: %s", err.Error())
260+
})
261+
})
262+
}

sg.config.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,8 @@ commands:
456456
# Connect to local development database, with the assumption that it will
457457
# have dotcom database tables.
458458
export DOTCOM_PGDSN_OVERRIDE="postgres://$PGUSER:$PGPASSWORD@$PGHOST:$PGPORT/$PGDATABASE?sslmode=$PGSSLMODE"
459+
# Enterprise Portal is responsible for generating license keys.
460+
export SOURCEGRAPH_LICENSE_GENERATION_KEY=$(cat ../dev-private/enterprise/dev/test-license-generation-key.pem)
459461
.bin/enterprise-portal
460462
install: |
461463
if [ -n "$DELVE" ]; then
@@ -2179,3 +2181,27 @@ tests:
21792181
See more details: https://docs-legacy.sourcegraph.com/dev/how-to/testing#running-integration-tests
21802182
21812183
cmd: pnpm test-integration:debug
2184+
2185+
enterprise-portal-e2e:
2186+
# After EP stops requiring dotcomdb, we can update this to just require
2187+
# `sg start -cmd enterprise-portal`
2188+
preamble:
2189+
An Enterprise Portal instance must be already running for these tests to
2190+
work, most commonly with `sg start dotcom`.
2191+
2192+
This will leave a lot of data behind in your local database - you can
2193+
delete everything using `psql -d sourcegraph -c TRUNCATE TABLE enterprise_portal_subscriptions CASCADE;`.
2194+
2195+
Subsequent runs do not conflict with existing data.
2196+
cmd: |
2197+
go test -v ./cmd/enterprise-portal/e2e/...
2198+
env:
2199+
EP_E2E_ENTERPRISEPORTAL_SERVER: "http://127.0.0.1:6081"
2200+
EP_E2E_SAMS_SERVER: "https://accounts.sgdev.org"
2201+
externalSecrets:
2202+
EP_E2E_SAMS_CLIENT_ID:
2203+
project: sourcegraph-local-dev
2204+
name: SG_LOCAL_DEV_SAMS_CLIENT_ID
2205+
EP_E2E_SAMS_CLIENT_SECRET:
2206+
project: sourcegraph-local-dev
2207+
name: SG_LOCAL_DEV_SAMS_CLIENT_SECRET

0 commit comments

Comments
 (0)