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 }