github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/settings/service_test.go (about)

     1  package settings
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/cozy/cozy-stack/model/cloudery"
     8  	"github.com/cozy/cozy-stack/model/instance"
     9  	"github.com/cozy/cozy-stack/model/token"
    10  	"github.com/cozy/cozy-stack/pkg/couchdb"
    11  	"github.com/cozy/cozy-stack/pkg/emailer"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func TestServiceImplems(t *testing.T) {
    16  	assert.Implements(t, (*Service)(nil), new(SettingsService))
    17  	assert.Implements(t, (*Service)(nil), new(Mock))
    18  }
    19  
    20  func Test_StartEmailUpdate_success(t *testing.T) {
    21  	emailerSvc := emailer.NewMock(t)
    22  	instSvc := instance.NewMock(t)
    23  	tokenSvc := token.NewMock(t)
    24  	clouderySvc := cloudery.NewMock(t)
    25  	storage := newStorageMock(t)
    26  
    27  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
    28  
    29  	inst := instance.Instance{
    30  		Domain: "foo.mycozy.cloud",
    31  	}
    32  
    33  	cmd := &UpdateEmailCmd{
    34  		Passphrase: []byte("some-pass"),
    35  		Email:      "some@email.com",
    36  	}
    37  
    38  	instSvc.On("CheckPassphrase", &inst, cmd.Passphrase).Return(nil).Once()
    39  
    40  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
    41  		M: map[string]interface{}{"public_name": "Jane Doe"},
    42  	}, nil).Twice()
    43  
    44  	tokenSvc.On("GenerateAndSave", &inst, token.EmailUpdate, "some@email.com", TokenExpiration).
    45  		Return("some-token", nil).Once()
    46  
    47  	storage.On("setInstanceSettings", &inst, &couchdb.JSONDoc{
    48  		M: map[string]interface{}{
    49  			"public_name":   "Jane Doe",
    50  			"pending_email": "some@email.com",
    51  		},
    52  	}).Return(nil).Once()
    53  
    54  	emailerSvc.On("SendPendingEmail", &inst, &emailer.TransactionalEmailCmd{
    55  		TemplateName: "update_email",
    56  		TemplateValues: map[string]interface{}{
    57  			"PublicName":      "Jane Doe",
    58  			"EmailUpdateLink": "http://foo.mycozy.cloud/settings/email/confirm?token=some-token",
    59  		},
    60  	}).Return(nil).Once()
    61  
    62  	err := svc.StartEmailUpdate(&inst, cmd)
    63  	assert.NoError(t, err)
    64  }
    65  
    66  func Test_StartEmailUpdate_with_an_invalid_password(t *testing.T) {
    67  	emailerSvc := emailer.NewMock(t)
    68  	instSvc := instance.NewMock(t)
    69  	tokenSvc := token.NewMock(t)
    70  	clouderySvc := cloudery.NewMock(t)
    71  	storage := newStorageMock(t)
    72  
    73  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
    74  
    75  	inst := instance.Instance{
    76  		Domain: "foo.mycozy.cloud",
    77  	}
    78  
    79  	cmd := &UpdateEmailCmd{
    80  		Passphrase: []byte("some-pass"),
    81  		Email:      "some@email.com",
    82  	}
    83  
    84  	instSvc.On("CheckPassphrase", &inst, cmd.Passphrase).Return(instance.ErrInvalidPassphrase).Once()
    85  
    86  	err := svc.StartEmailUpdate(&inst, cmd)
    87  	assert.ErrorIs(t, err, instance.ErrInvalidPassphrase)
    88  }
    89  
    90  func Test_StartEmailUpdate_with_a_missing_public_name(t *testing.T) {
    91  	emailerSvc := emailer.NewMock(t)
    92  	instSvc := instance.NewMock(t)
    93  	tokenSvc := token.NewMock(t)
    94  	clouderySvc := cloudery.NewMock(t)
    95  	storage := newStorageMock(t)
    96  
    97  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
    98  
    99  	inst := instance.Instance{
   100  		Domain: "foo.mycozy.cloud",
   101  	}
   102  
   103  	cmd := &UpdateEmailCmd{
   104  		Passphrase: []byte("some-pass"),
   105  		Email:      "some@email.com",
   106  	}
   107  
   108  	instSvc.On("CheckPassphrase", &inst, cmd.Passphrase).Return(nil).Once()
   109  
   110  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   111  		// No field "public_name"
   112  		M: map[string]interface{}{},
   113  	}, nil).Twice()
   114  
   115  	tokenSvc.On("GenerateAndSave", &inst, token.EmailUpdate, "some@email.com", TokenExpiration).
   116  		Return("some-token", nil).Once()
   117  
   118  	storage.On("setInstanceSettings", &inst, &couchdb.JSONDoc{
   119  		M: map[string]interface{}{
   120  			// There is no public name
   121  			"pending_email": "some@email.com",
   122  		},
   123  	}).Return(nil).Once()
   124  
   125  	emailerSvc.On("SendPendingEmail", &inst, &emailer.TransactionalEmailCmd{
   126  		TemplateName: "update_email",
   127  		TemplateValues: map[string]interface{}{
   128  			"PublicName":      "foo", // Change here
   129  			"EmailUpdateLink": "http://foo.mycozy.cloud/settings/email/confirm?token=some-token",
   130  		},
   131  	}).Return(nil).Once()
   132  
   133  	err := svc.StartEmailUpdate(&inst, cmd)
   134  	assert.NoError(t, err)
   135  }
   136  
   137  func TestConfirmEmailUpdate_success(t *testing.T) {
   138  	emailerSvc := emailer.NewMock(t)
   139  	instSvc := instance.NewMock(t)
   140  	tokenSvc := token.NewMock(t)
   141  	clouderySvc := cloudery.NewMock(t)
   142  	storage := newStorageMock(t)
   143  
   144  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   145  
   146  	inst := instance.Instance{
   147  		Domain: "foo.mycozy.cloud",
   148  		Locale: "fr/FR",
   149  	}
   150  
   151  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   152  		M: map[string]interface{}{
   153  			"public_name":   "Jane Doe",
   154  			"email":         "foo@bar.baz",
   155  			"pending_email": "some@email.com",
   156  		},
   157  	}, nil).Once()
   158  
   159  	tokenSvc.On("Validate", &inst, token.EmailUpdate, "some@email.com", "some-token").
   160  		Return(nil).Once()
   161  
   162  	storage.On("setInstanceSettings", &inst, &couchdb.JSONDoc{
   163  		M: map[string]interface{}{
   164  			"public_name":   "Jane Doe",
   165  			"email":         "some@email.com",
   166  			"pending_email": nil,
   167  		},
   168  	}).Return(nil).Once()
   169  
   170  	clouderySvc.On("SaveInstance", &inst, &cloudery.SaveCmd{
   171  		Locale:     "fr/FR",
   172  		Email:      "some@email.com",
   173  		PublicName: "Jane Doe",
   174  	}).Return(nil).Once()
   175  
   176  	err := svc.ConfirmEmailUpdate(&inst, "some-token")
   177  	assert.NoError(t, err)
   178  }
   179  
   180  func TestConfirmEmailUpdate_with_an_invalid_token(t *testing.T) {
   181  	emailerSvc := emailer.NewMock(t)
   182  	instSvc := instance.NewMock(t)
   183  	tokenSvc := token.NewMock(t)
   184  	clouderySvc := cloudery.NewMock(t)
   185  	storage := newStorageMock(t)
   186  
   187  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   188  
   189  	inst := instance.Instance{
   190  		Domain: "foo.mycozy.cloud",
   191  	}
   192  
   193  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   194  		M: map[string]interface{}{
   195  			"public_name":   "Jane Doe",
   196  			"email":         "foo@bar.baz",
   197  			"pending_email": "some@email.com",
   198  		},
   199  	}, nil).Once()
   200  
   201  	tokenSvc.On("Validate", &inst, token.EmailUpdate, "some@email.com", "some-invalid-token").
   202  		Return(token.ErrInvalidToken).Once()
   203  
   204  	err := svc.ConfirmEmailUpdate(&inst, "some-invalid-token")
   205  	assert.ErrorIs(t, err, token.ErrInvalidToken)
   206  }
   207  
   208  func TestConfirmEmailUpdate_without_a_pending_email(t *testing.T) {
   209  	emailerSvc := emailer.NewMock(t)
   210  	instSvc := instance.NewMock(t)
   211  	tokenSvc := token.NewMock(t)
   212  	clouderySvc := cloudery.NewMock(t)
   213  	storage := newStorageMock(t)
   214  
   215  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   216  
   217  	inst := instance.Instance{
   218  		Domain: "foo.mycozy.cloud",
   219  	}
   220  
   221  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   222  		M: map[string]interface{}{
   223  			"public_name": "Jane Doe",
   224  			"email":       "foo@bar.baz",
   225  			// There is no pending_email
   226  		},
   227  	}, nil).Once()
   228  
   229  	err := svc.ConfirmEmailUpdate(&inst, "some-token")
   230  	assert.ErrorIs(t, err, ErrNoPendingEmail)
   231  }
   232  
   233  func Test_CancelEmailUpdate_success(t *testing.T) {
   234  	emailerSvc := emailer.NewMock(t)
   235  	instSvc := instance.NewMock(t)
   236  	tokenSvc := token.NewMock(t)
   237  	clouderySvc := cloudery.NewMock(t)
   238  	storage := newStorageMock(t)
   239  
   240  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   241  
   242  	inst := instance.Instance{
   243  		Domain: "foo.mycozy.cloud",
   244  	}
   245  
   246  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   247  		M: map[string]interface{}{
   248  			"public_name":   "Jane Doe",
   249  			"email":         "foo@bar.baz",
   250  			"pending_email": "some@email.com",
   251  		},
   252  	}, nil).Once()
   253  
   254  	storage.On("setInstanceSettings", &inst, &couchdb.JSONDoc{
   255  		M: map[string]interface{}{
   256  			"public_name":   "Jane Doe",
   257  			"email":         "foo@bar.baz",
   258  			"pending_email": nil,
   259  		},
   260  	}).Return(nil).Once()
   261  
   262  	err := svc.CancelEmailUpdate(&inst)
   263  	assert.NoError(t, err)
   264  }
   265  
   266  func Test_CancelEmailUpdate_without_pending_email(t *testing.T) {
   267  	emailerSvc := emailer.NewMock(t)
   268  	instSvc := instance.NewMock(t)
   269  	tokenSvc := token.NewMock(t)
   270  	clouderySvc := cloudery.NewMock(t)
   271  	storage := newStorageMock(t)
   272  
   273  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   274  
   275  	inst := instance.Instance{
   276  		Domain: "foo.mycozy.cloud",
   277  	}
   278  
   279  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   280  		M: map[string]interface{}{
   281  			"public_name": "Jane Doe",
   282  			"email":       "foo@bar.baz",
   283  		},
   284  	}, nil).Once()
   285  
   286  	err := svc.CancelEmailUpdate(&inst)
   287  	assert.NoError(t, err)
   288  }
   289  
   290  func Test_ResendEmailUpdate_success(t *testing.T) {
   291  	emailerSvc := emailer.NewMock(t)
   292  	instSvc := instance.NewMock(t)
   293  	tokenSvc := token.NewMock(t)
   294  	clouderySvc := cloudery.NewMock(t)
   295  	storage := newStorageMock(t)
   296  
   297  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   298  
   299  	inst := instance.Instance{
   300  		Domain: "foo.mycozy.cloud",
   301  	}
   302  
   303  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   304  		M: map[string]interface{}{
   305  			"public_name":   "Jane Doe",
   306  			"pending_email": "foo.mycozy.cloud",
   307  		},
   308  	}, nil).Twice()
   309  
   310  	tokenSvc.On("GenerateAndSave", &inst, token.EmailUpdate, "foo.mycozy.cloud", TokenExpiration).
   311  		Return("some-token", nil).Once()
   312  
   313  	emailerSvc.On("SendPendingEmail", &inst, &emailer.TransactionalEmailCmd{
   314  		TemplateName: "update_email",
   315  		TemplateValues: map[string]interface{}{
   316  			"PublicName":      "Jane Doe",
   317  			"EmailUpdateLink": "http://foo.mycozy.cloud/settings/email/confirm?token=some-token",
   318  		},
   319  	}).Return(nil).Once()
   320  
   321  	err := svc.ResendEmailUpdate(&inst)
   322  	assert.NoError(t, err)
   323  }
   324  
   325  func Test_ResendEmailUpdate_with_no_pending_email(t *testing.T) {
   326  	emailerSvc := emailer.NewMock(t)
   327  	instSvc := instance.NewMock(t)
   328  	tokenSvc := token.NewMock(t)
   329  	clouderySvc := cloudery.NewMock(t)
   330  	storage := newStorageMock(t)
   331  
   332  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   333  
   334  	inst := instance.Instance{
   335  		Domain: "foo.mycozy.cloud",
   336  	}
   337  
   338  	storage.On("getInstanceSettings", &inst).Return(&couchdb.JSONDoc{
   339  		M: map[string]interface{}{
   340  			"public_name": "Jane Doe",
   341  			// no pendin_email
   342  		},
   343  	}, nil).Twice()
   344  
   345  	err := svc.ResendEmailUpdate(&inst)
   346  	assert.ErrorIs(t, err, ErrNoPendingEmail)
   347  }
   348  
   349  func Test_GetExternalTies(t *testing.T) {
   350  	emailerSvc := emailer.NewMock(t)
   351  	instSvc := instance.NewMock(t)
   352  	tokenSvc := token.NewMock(t)
   353  	clouderySvc := cloudery.NewMock(t)
   354  	storage := newStorageMock(t)
   355  
   356  	svc := NewService(emailerSvc, instSvc, tokenSvc, clouderySvc, storage)
   357  
   358  	inst := instance.Instance{
   359  		Domain: "foo.mycozy.cloud",
   360  	}
   361  
   362  	t.Run("with blocking subscription", func(t *testing.T) {
   363  		blockingSubscription := cloudery.BlockingSubscription{Vendor: "ios"}
   364  
   365  		clouderySvc.On("BlockingSubscription", &inst).Return(&blockingSubscription, nil).Once()
   366  
   367  		ties, err := svc.GetExternalTies(&inst)
   368  		assert.NoError(t, err)
   369  		assert.EqualExportedValues(t, ExternalTies{HasBlockingSubscription: true, BlockingSubscription: &blockingSubscription}, *ties)
   370  	})
   371  
   372  	t.Run("without blocking subscription", func(t *testing.T) {
   373  		clouderySvc.On("BlockingSubscription", &inst).Return(nil, nil).Once()
   374  
   375  		ties, err := svc.GetExternalTies(&inst)
   376  		assert.NoError(t, err)
   377  		assert.EqualExportedValues(t, ExternalTies{HasBlockingSubscription: false}, *ties)
   378  	})
   379  
   380  	t.Run("with error from cloudery", func(t *testing.T) {
   381  		unauthorizedError := errors.New("unauthorized")
   382  		clouderySvc.On("BlockingSubscription", &inst).Return(nil, unauthorizedError).Once()
   383  
   384  		ties, err := svc.GetExternalTies(&inst)
   385  		assert.ErrorIs(t, err, unauthorizedError)
   386  		assert.Nil(t, ties)
   387  	})
   388  }