1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package consoleql_test
5
6import (
7	"context"
8	"fmt"
9	"testing"
10	"time"
11
12	"github.com/graphql-go/graphql"
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15	"go.uber.org/zap/zaptest"
16
17	"storj.io/common/testcontext"
18	"storj.io/common/testrand"
19	"storj.io/common/uuid"
20	"storj.io/storj/private/post"
21	"storj.io/storj/private/testplanet"
22	"storj.io/storj/private/testredis"
23	"storj.io/storj/satellite/accounting"
24	"storj.io/storj/satellite/accounting/live"
25	"storj.io/storj/satellite/analytics"
26	"storj.io/storj/satellite/console"
27	"storj.io/storj/satellite/console/consoleauth"
28	"storj.io/storj/satellite/console/consoleweb/consoleql"
29	"storj.io/storj/satellite/mailservice"
30	"storj.io/storj/satellite/payments/paymentsconfig"
31	"storj.io/storj/satellite/payments/stripecoinpayments"
32	"storj.io/storj/satellite/rewards"
33)
34
35// discardSender discard sending of an actual email.
36type discardSender struct{}
37
38// SendEmail immediately returns with nil error.
39func (*discardSender) SendEmail(ctx context.Context, msg *post.Message) error {
40	return nil
41}
42
43// FromAddress returns empty post.Address.
44func (*discardSender) FromAddress() post.Address {
45	return post.Address{}
46}
47
48func TestGraphqlMutation(t *testing.T) {
49	testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
50		sat := planet.Satellites[0]
51		db := sat.DB
52		log := zaptest.NewLogger(t)
53
54		partnersService := rewards.NewPartnersService(
55			log.Named("partners"),
56			rewards.DefaultPartnersDB,
57		)
58
59		analyticsService := analytics.NewService(log, analytics.Config{}, "test-satellite")
60
61		redis, err := testredis.Mini(ctx)
62		require.NoError(t, err)
63		defer ctx.Check(redis.Close)
64
65		cache, err := live.OpenCache(ctx, log.Named("cache"), live.Config{StorageBackend: "redis://" + redis.Addr() + "?db=0"})
66		require.NoError(t, err)
67
68		projectLimitCache := accounting.NewProjectLimitCache(db.ProjectAccounting(), 0, 0, 0, accounting.ProjectLimitConfig{CacheCapacity: 100})
69
70		projectUsage := accounting.NewService(db.ProjectAccounting(), cache, projectLimitCache, 5*time.Minute, -10*time.Second)
71
72		// TODO maybe switch this test to testplanet to avoid defining config and Stripe service
73		pc := paymentsconfig.Config{
74			StorageTBPrice: "10",
75			EgressTBPrice:  "45",
76			SegmentPrice:   "0.0000022",
77		}
78
79		paymentsService, err := stripecoinpayments.NewService(
80			log.Named("payments.stripe:service"),
81			stripecoinpayments.NewStripeMock(
82				testrand.NodeID(),
83				db.StripeCoinPayments().Customers(),
84				db.Console().Users(),
85			),
86			pc.StripeCoinPayments,
87			db.StripeCoinPayments(),
88			db.Console().Projects(),
89			db.ProjectAccounting(),
90			pc.StorageTBPrice,
91			pc.EgressTBPrice,
92			pc.SegmentPrice,
93			pc.BonusRate)
94		require.NoError(t, err)
95
96		service, err := console.NewService(
97			log.Named("console"),
98			&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
99			db.Console(),
100			db.ProjectAccounting(),
101			projectUsage,
102			sat.API.Buckets.Service,
103			partnersService,
104			paymentsService.Accounts(),
105			analyticsService,
106			console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5},
107		)
108		require.NoError(t, err)
109
110		mailService, err := mailservice.New(log, &discardSender{}, "testdata")
111		require.NoError(t, err)
112		defer ctx.Check(mailService.Close)
113
114		rootObject := make(map[string]interface{})
115		rootObject["origin"] = "http://doesntmatter.com/"
116		rootObject[consoleql.ActivationPath] = "?activationToken="
117		rootObject[consoleql.SignInPath] = "login"
118		rootObject[consoleql.LetUsKnowURL] = "letUsKnowURL"
119		rootObject[consoleql.ContactInfoURL] = "contactInfoURL"
120		rootObject[consoleql.TermsAndConditionsURL] = "termsAndConditionsURL"
121
122		schema, err := consoleql.CreateSchema(log, service, mailService)
123		require.NoError(t, err)
124
125		createUser := console.CreateUser{
126			FullName:  "John Roll",
127			ShortName: "Roll",
128			Email:     "test@mail.test",
129			UserAgent: []byte("120bf202-8252-437e-ac12-0e364bee852e"),
130			Password:  "123a123",
131		}
132
133		regToken, err := service.CreateRegToken(ctx, 1)
134		require.NoError(t, err)
135
136		rootUser, err := service.CreateUser(ctx, createUser, regToken.Secret)
137		require.NoError(t, err)
138		require.Equal(t, createUser.UserAgent, rootUser.UserAgent)
139
140		err = paymentsService.Accounts().Setup(ctx, rootUser.ID, rootUser.Email)
141		require.NoError(t, err)
142
143		activationToken, err := service.GenerateActivationToken(ctx, rootUser.ID, rootUser.Email)
144		require.NoError(t, err)
145
146		_, err = service.ActivateAccount(ctx, activationToken)
147		require.NoError(t, err)
148
149		token, err := service.Token(ctx, console.AuthUser{Email: createUser.Email, Password: createUser.Password})
150		require.NoError(t, err)
151
152		sauth, err := service.Authorize(consoleauth.WithAPIKey(ctx, []byte(token)))
153		require.NoError(t, err)
154
155		authCtx := console.WithAuth(ctx, sauth)
156
157		testQuery := func(t *testing.T, query string) (interface{}, error) {
158			result := graphql.Do(graphql.Params{
159				Schema:        schema,
160				Context:       authCtx,
161				RequestString: query,
162				RootObject:    rootObject,
163			})
164
165			for _, err := range result.Errors {
166				if err.OriginalError() != nil {
167					return nil, err
168				}
169			}
170			require.False(t, result.HasErrors())
171
172			return result.Data, nil
173		}
174
175		token, err = service.Token(ctx, console.AuthUser{Email: rootUser.Email, Password: createUser.Password})
176		require.NoError(t, err)
177
178		sauth, err = service.Authorize(consoleauth.WithAPIKey(ctx, []byte(token)))
179		require.NoError(t, err)
180
181		authCtx = console.WithAuth(ctx, sauth)
182
183		var projectIDField string
184		t.Run("Create project mutation", func(t *testing.T) {
185			projectInfo := console.ProjectInfo{
186				Name:        "Project name",
187				Description: "desc",
188			}
189
190			query := fmt.Sprintf(
191				"mutation {createProject(input:{name:\"%s\",description:\"%s\"}){name,description,id,createdAt}}",
192				projectInfo.Name,
193				projectInfo.Description,
194			)
195
196			result, err := testQuery(t, query)
197			require.NoError(t, err)
198
199			data := result.(map[string]interface{})
200			project := data[consoleql.CreateProjectMutation].(map[string]interface{})
201
202			assert.Equal(t, projectInfo.Name, project[consoleql.FieldName])
203			assert.Equal(t, projectInfo.Description, project[consoleql.FieldDescription])
204
205			projectIDField = project[consoleql.FieldID].(string)
206		})
207
208		projectID, err := uuid.FromString(projectIDField)
209		require.NoError(t, err)
210
211		project, err := service.GetProject(authCtx, projectID)
212		require.NoError(t, err)
213		require.Equal(t, rootUser.PartnerID, project.PartnerID)
214
215		regTokenUser1, err := service.CreateRegToken(ctx, 1)
216		require.NoError(t, err)
217
218		user1, err := service.CreateUser(authCtx, console.CreateUser{
219			FullName: "User1",
220			Email:    "u1@mail.test",
221			Password: "123a123",
222		}, regTokenUser1.Secret)
223		require.NoError(t, err)
224
225		t.Run("Activation", func(t *testing.T) {
226			activationToken1, err := service.GenerateActivationToken(
227				ctx,
228				user1.ID,
229				"u1@mail.test",
230			)
231			require.NoError(t, err)
232
233			_, err = service.ActivateAccount(ctx, activationToken1)
234			require.NoError(t, err)
235
236			user1.Email = "u1@mail.test"
237		})
238
239		regTokenUser2, err := service.CreateRegToken(ctx, 1)
240		require.NoError(t, err)
241
242		user2, err := service.CreateUser(authCtx, console.CreateUser{
243			FullName: "User1",
244			Email:    "u2@mail.test",
245			Password: "123a123",
246		}, regTokenUser2.Secret)
247		require.NoError(t, err)
248
249		t.Run("Activation", func(t *testing.T) {
250			activationToken2, err := service.GenerateActivationToken(
251				ctx,
252				user2.ID,
253				"u2@mail.test",
254			)
255			require.NoError(t, err)
256
257			_, err = service.ActivateAccount(ctx, activationToken2)
258			require.NoError(t, err)
259
260			user2.Email = "u2@mail.test"
261		})
262
263		t.Run("Add project members mutation", func(t *testing.T) {
264			query := fmt.Sprintf(
265				"mutation {addProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(cursor: { limit: 50, search: \"\", page: 1, order: 1, orderDirection: 2 }){projectMembers{joinedAt}}}}",
266				project.ID.String(),
267				user1.Email,
268				user2.Email,
269			)
270
271			result, err := testQuery(t, query)
272			require.NoError(t, err)
273
274			data := result.(map[string]interface{})
275			proj := data[consoleql.AddProjectMembersMutation].(map[string]interface{})
276
277			members := proj[consoleql.FieldMembers].(map[string]interface{})
278			projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
279
280			assert.Equal(t, project.ID.String(), proj[consoleql.FieldID])
281			assert.Equal(t, project.Name, proj[consoleql.FieldName])
282			assert.Equal(t, 3, len(projectMembers))
283		})
284
285		t.Run("Delete project members mutation", func(t *testing.T) {
286			query := fmt.Sprintf(
287				"mutation {deleteProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(cursor: { limit: 50, search: \"\", page: 1, order: 1, orderDirection: 2 }){projectMembers{user{id}}}}}",
288				project.ID.String(),
289				user1.Email,
290				user2.Email,
291			)
292
293			result, err := testQuery(t, query)
294			require.NoError(t, err)
295
296			data := result.(map[string]interface{})
297			proj := data[consoleql.DeleteProjectMembersMutation].(map[string]interface{})
298
299			members := proj[consoleql.FieldMembers].(map[string]interface{})
300			projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
301			rootMember := projectMembers[0].(map[string]interface{})[consoleql.UserType].(map[string]interface{})
302
303			assert.Equal(t, project.ID.String(), proj[consoleql.FieldID])
304			assert.Equal(t, project.Name, proj[consoleql.FieldName])
305			assert.Equal(t, 1, len(members))
306
307			assert.Equal(t, rootUser.ID.String(), rootMember[consoleql.FieldID])
308		})
309
310		var keyID string
311		t.Run("Create api key mutation", func(t *testing.T) {
312			keyName := "key1"
313			query := fmt.Sprintf(
314				"mutation {createAPIKey(projectID:\"%s\",name:\"%s\"){key,keyInfo{id,name,projectID,partnerId}}}",
315				project.ID.String(),
316				keyName,
317			)
318
319			result, err := testQuery(t, query)
320			require.NoError(t, err)
321
322			data := result.(map[string]interface{})
323			createAPIKey := data[consoleql.CreateAPIKeyMutation].(map[string]interface{})
324
325			key := createAPIKey[consoleql.FieldKey].(string)
326			keyInfo := createAPIKey[consoleql.APIKeyInfoType].(map[string]interface{})
327
328			assert.NotEqual(t, "", key)
329
330			assert.Equal(t, keyName, keyInfo[consoleql.FieldName])
331			assert.Equal(t, project.ID.String(), keyInfo[consoleql.FieldProjectID])
332			assert.Equal(t, rootUser.PartnerID.String(), keyInfo[consoleql.FieldPartnerID])
333
334			keyID = keyInfo[consoleql.FieldID].(string)
335		})
336
337		t.Run("Delete api key mutation", func(t *testing.T) {
338			id, err := uuid.FromString(keyID)
339			require.NoError(t, err)
340
341			info, err := service.GetAPIKeyInfo(authCtx, id)
342			require.NoError(t, err)
343
344			query := fmt.Sprintf(
345				"mutation {deleteAPIKeys(id:[\"%s\"]){name,projectID}}",
346				keyID,
347			)
348
349			result, err := testQuery(t, query)
350			require.NoError(t, err)
351
352			data := result.(map[string]interface{})
353			keyInfoList := data[consoleql.DeleteAPIKeysMutation].([]interface{})
354
355			for _, k := range keyInfoList {
356				keyInfo := k.(map[string]interface{})
357
358				assert.Equal(t, info.Name, keyInfo[consoleql.FieldName])
359				assert.Equal(t, project.ID.String(), keyInfo[consoleql.FieldProjectID])
360			}
361		})
362
363		const testName = "testName"
364		const testDescription = "test description"
365		const StorageLimit = "100"
366		const BandwidthLimit = "100"
367
368		t.Run("Update project mutation", func(t *testing.T) {
369			query := fmt.Sprintf(
370				"mutation {updateProject(id:\"%s\",projectFields:{name:\"%s\",description:\"%s\"},projectLimits:{storageLimit:\"%s\",bandwidthLimit:\"%s\"}){id,name,description}}",
371				project.ID.String(),
372				testName,
373				testDescription,
374				StorageLimit,
375				BandwidthLimit,
376			)
377
378			result, err := testQuery(t, query)
379			require.NoError(t, err)
380
381			data := result.(map[string]interface{})
382			proj := data[consoleql.UpdateProjectMutation].(map[string]interface{})
383
384			assert.Equal(t, project.ID.String(), proj[consoleql.FieldID])
385			assert.Equal(t, testName, proj[consoleql.FieldName])
386			assert.Equal(t, testDescription, proj[consoleql.FieldDescription])
387		})
388
389		t.Run("Delete project mutation", func(t *testing.T) {
390			query := fmt.Sprintf(
391				"mutation {deleteProject(id:\"%s\"){id,name}}",
392				projectID,
393			)
394
395			result, err := testQuery(t, query)
396			require.Error(t, err)
397			require.Nil(t, result)
398			require.Equal(t, console.ErrUnauthorized.New("not implemented").Error(), err.Error())
399		})
400	})
401}
402