diff --git a/pkg/signup/service/signup_service.go b/pkg/signup/service/signup_service.go index 3ba62cc0..463a3feb 100644 --- a/pkg/signup/service/signup_service.go +++ b/pkg/signup/service/signup_service.go @@ -6,6 +6,7 @@ import ( "regexp" "sort" "strings" + "time" toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" "github.com/codeready-toolchain/registration-service/pkg/application/service" @@ -458,6 +459,10 @@ func (s *ServiceImpl) DoGetSignup(ctx *gin.Context, provider ResourceProvider, u return signupResponse, nil } + if !userSignup.Status.ScheduledDeactivationTimestamp.IsZero() { + signupResponse.EndDate = userSignup.Status.ScheduledDeactivationTimestamp.UTC().Format(time.RFC3339) + } + // If UserSignup status is complete as active // Retrieve MasterUserRecord resource from the host cluster and use its status mur, err := provider.GetMasterUserRecord(userSignup.Status.CompliantUsername) @@ -475,6 +480,11 @@ func (s *ServiceImpl) DoGetSignup(ctx *gin.Context, provider ResourceProvider, u Message: murCondition.Message, VerificationRequired: states.VerificationRequired(userSignup), } + + if mur.Status.ProvisionedTime != nil { + signupResponse.StartDate = mur.Status.ProvisionedTime.UTC().Format(time.RFC3339) + } + memberCluster, defaultNamespace := GetDefaultUserTarget(provider, userSignup.Status.HomeSpace, mur.Name) if memberCluster != "" { // Retrieve cluster-specific URLs from the status of the corresponding member cluster diff --git a/pkg/signup/service/signup_service_test.go b/pkg/signup/service/signup_service_test.go index 97f1168c..c8d07e0c 100644 --- a/pkg/signup/service/signup_service_test.go +++ b/pkg/signup/service/signup_service_test.go @@ -4,11 +4,13 @@ import ( "bytes" "errors" "fmt" + "github.com/codeready-toolchain/registration-service/pkg/util" "hash/crc32" "net/http" "net/http/httptest" "strings" "testing" + "time" "github.com/codeready-toolchain/registration-service/pkg/application/service/factory" "github.com/codeready-toolchain/registration-service/pkg/configuration" @@ -839,6 +841,8 @@ func (s *TestSignupServiceSuite) TestGetSignupStatusNotComplete() { require.Empty(s.T(), response.ProxyURL) assert.Empty(s.T(), response.DefaultUserNamespace) assert.Empty(s.T(), response.RHODSMemberURL) + assert.Empty(s.T(), response.StartDate) + assert.Empty(s.T(), response.EndDate) s.T().Run("informer - with check for usersignup complete condition", func(t *testing.T) { // given @@ -1158,6 +1162,9 @@ func (s *TestSignupServiceSuite) TestGetSignupStatusOK() { require.Equal(t, us.Name, response.Name) require.Equal(t, "jsmith", response.Username) require.Equal(t, "ted", response.CompliantUsername) + + require.Equal(t, mur.Status.ProvisionedTime.Format(time.RFC3339), response.StartDate) + require.Equal(t, us.Status.ScheduledDeactivationTimestamp.Format(time.RFC3339), response.EndDate) assert.True(t, response.Status.Ready) assert.Equal(t, "mur_ready_reason", response.Status.Reason) assert.Equal(t, "mur_ready_message", response.Status.Message) @@ -1234,13 +1241,26 @@ func (s *TestSignupServiceSuite) TestGetSignupByUsernameOK() { us := s.newUserSignupComplete() us.Name = service.EncodeUserIdentifier(us.Spec.IdentityClaims.PreferredUsername) + // Set the scheduled deactivation timestamp 1 day in the future + deactivationTimestamp := time.Now().Add(time.Hour * 24).Round(time.Second).UTC() + us.Status.ScheduledDeactivationTimestamp = util.Ptr(v1.NewTime(deactivationTimestamp)) err := s.FakeUserSignupClient.Tracker.Add(us) require.NoError(s.T(), err) mur := s.newProvisionedMUR("ted") + // Set the provisioned time 29 days in the past + provisionedTime := time.Now().Add(-time.Hour * 24 * 29).Round(time.Second) + mur.Status.ProvisionedTime = util.Ptr(v1.NewTime(provisionedTime)) err = s.FakeMasterUserRecordClient.Tracker.Add(mur) require.NoError(s.T(), err) + svc := service.NewSignupService( + fake.MemberClusterServiceContext{ + Client: s, + Svcs: s.Application, + }, + ) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) space := s.newSpace(mur.Name) @@ -1256,12 +1276,20 @@ func (s *TestSignupServiceSuite) TestGetSignupByUsernameOK() { require.NoError(s.T(), err) // when - response, err := s.Application.SignupService().GetSignup(c, "foo", us.Spec.IdentityClaims.PreferredUsername) + response, err := svc.GetSignup(c, "foo", us.Spec.IdentityClaims.PreferredUsername) // then require.NoError(s.T(), err) require.NotNil(s.T(), response) + // Confirm the StartDate is the same as the provisionedTime + require.Equal(s.T(), provisionedTime.UTC().Format(time.RFC3339), response.StartDate) + + // Confirm the end date is about the same as the deactivationTimestamp + responseEndDate, err := time.ParseInLocation(time.RFC3339, response.EndDate, nil) + require.NoError(s.T(), err) + require.Equal(s.T(), deactivationTimestamp, responseEndDate) + require.Equal(s.T(), us.Name, response.Name) require.Equal(s.T(), "jsmith", response.Username) require.Equal(s.T(), "ted", response.CompliantUsername) @@ -2228,6 +2256,7 @@ func (s *TestSignupServiceSuite) newUserSignupCompleteWithReason(reason string) }, }, Status: toolchainv1alpha1.UserSignupStatus{ + ScheduledDeactivationTimestamp: util.Ptr(v1.NewTime(time.Now().Add(30 * time.Hour * 24))), Conditions: []toolchainv1alpha1.Condition{ { Type: toolchainv1alpha1.UserSignupComplete, @@ -2302,6 +2331,7 @@ func (s *TestSignupServiceSuite) newProvisionedMUR(name string) *toolchainv1alph UserAccounts: []toolchainv1alpha1.UserAccountEmbedded{{TargetCluster: "member-123"}}, }, Status: toolchainv1alpha1.MasterUserRecordStatus{ + ProvisionedTime: util.Ptr(v1.NewTime(time.Now())), Conditions: []toolchainv1alpha1.Condition{ { Type: toolchainv1alpha1.MasterUserRecordReady, diff --git a/pkg/signup/signup.go b/pkg/signup/signup.go index 2697e129..663e8e76 100644 --- a/pkg/signup/signup.go +++ b/pkg/signup/signup.go @@ -40,6 +40,10 @@ type Signup struct { // Company from the Identity Provider Company string `json:"company"` Status Status `json:"status,omitempty"` + // StartDate is the date that the user's current subscription started, in RFC3339 format + StartDate string `json:"startDate,omitempty"` + // End Date is the date that the user's current subscription will end, in RFC3339 format + EndDate string `json:"endDate,omitempty"` } // Status represents UserSignup resource status diff --git a/pkg/util/utils.go b/pkg/util/utils.go new file mode 100644 index 00000000..392597bd --- /dev/null +++ b/pkg/util/utils.go @@ -0,0 +1,6 @@ +package util + +// Ptr is a generic function that returns a pointer to whatever value is passed in +func Ptr[T any](v T) *T { + return &v +}