github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/instance/lifecycle/lifecycle_test.go (about)

     1  package lifecycle_test
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/cozy/cozy-stack/model/bitwarden/settings"
     9  	"github.com/cozy/cozy-stack/model/instance"
    10  	"github.com/cozy/cozy-stack/model/instance/lifecycle"
    11  	"github.com/cozy/cozy-stack/model/stack"
    12  	"github.com/cozy/cozy-stack/model/vfs"
    13  	"github.com/cozy/cozy-stack/pkg/config/config"
    14  	"github.com/cozy/cozy-stack/pkg/consts"
    15  	"github.com/cozy/cozy-stack/pkg/couchdb"
    16  	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
    17  	"github.com/cozy/cozy-stack/pkg/prefixer"
    18  	"github.com/cozy/cozy-stack/tests/testutils"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  
    22  	_ "github.com/cozy/cozy-stack/worker/mails"
    23  )
    24  
    25  func TestLifecycle(t *testing.T) {
    26  	if testing.Short() {
    27  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    28  	}
    29  
    30  	config.UseTestFile(t)
    31  
    32  	testutils.NeedCouchdb(t)
    33  
    34  	_, _, err := stack.Start()
    35  	require.NoError(t, err)
    36  
    37  	t.Cleanup(cleanInstance)
    38  
    39  	t.Run("ChooseCouchCluster", func(t *testing.T) {
    40  		clusters := []config.CouchDBCluster{
    41  			{Creation: false},
    42  		}
    43  		_, err := lifecycle.ChooseCouchCluster(clusters)
    44  		assert.Error(t, err)
    45  
    46  		clusters = []config.CouchDBCluster{
    47  			{Creation: false},
    48  			{Creation: true},
    49  		}
    50  		index, err := lifecycle.ChooseCouchCluster(clusters)
    51  		assert.NoError(t, err)
    52  		assert.Equal(t, 1, index)
    53  
    54  		clusters = []config.CouchDBCluster{
    55  			{Creation: false},
    56  			{Creation: true},
    57  			{Creation: true},
    58  			{Creation: false},
    59  			{Creation: true},
    60  		}
    61  		counts := make([]int, len(clusters))
    62  		for i := 0; i < 10000; i++ {
    63  			index, err = lifecycle.ChooseCouchCluster(clusters)
    64  			assert.NoError(t, err)
    65  			counts[index] += 1
    66  		}
    67  		assert.Equal(t, 0, counts[0])
    68  		assert.Greater(t, counts[1], 3000)
    69  		assert.Greater(t, counts[2], 3000)
    70  		assert.Equal(t, 0, counts[3])
    71  		assert.Greater(t, counts[4], 3000)
    72  	})
    73  
    74  	t.Run("GetInstanceNoDB", func(t *testing.T) {
    75  		res, err := lifecycle.GetInstance("no.instance.cozycloud.cc")
    76  		if assert.Error(t, err, "An error is expected") {
    77  			assert.Nil(t, res)
    78  			assert.ErrorIs(t, err, instance.ErrNotFound)
    79  		}
    80  	})
    81  
    82  	t.Run("CreateInstance", func(t *testing.T) {
    83  		instance, err := lifecycle.Create(&lifecycle.Options{
    84  			Domain: "test.cozycloud.cc",
    85  			Locale: "en",
    86  		})
    87  		if assert.NoError(t, err) {
    88  			assert.NotEmpty(t, instance.ID())
    89  			assert.Equal(t, instance.Domain, "test.cozycloud.cc")
    90  		}
    91  	})
    92  
    93  	t.Run("CreateInstanceWithFewSettings", func(t *testing.T) {
    94  		inst, err := lifecycle.Create(&lifecycle.Options{
    95  			Domain:     "test2.cozycloud.cc",
    96  			Timezone:   "Europe/Berlin",
    97  			Email:      "alice@example.com",
    98  			PublicName: "Alice",
    99  			Passphrase: "password",
   100  			Settings:   "offer:freemium,context:my_context,auth_mode:two_factor_mail,uuid:XXX,locale:en,tos:20151111,oidc_id:oidc_42",
   101  		})
   102  
   103  		assert.NoError(t, err)
   104  		assert.Equal(t, inst.Domain, "test2.cozycloud.cc")
   105  		doc, err := inst.SettingsDocument()
   106  		assert.NoError(t, err)
   107  		assert.Equal(t, "Europe/Berlin", doc.M["tz"].(string))
   108  		assert.Equal(t, "alice@example.com", doc.M["email"].(string))
   109  		assert.Equal(t, "freemium", doc.M["offer"].(string))
   110  		assert.Equal(t, "Alice", doc.M["public_name"].(string))
   111  
   112  		assert.Equal(t, inst.UUID, "XXX")
   113  		assert.Equal(t, inst.OIDCID, "oidc_42")
   114  		assert.Equal(t, inst.Locale, "en")
   115  		assert.Equal(t, inst.TOSSigned, "1.0.0-20151111")
   116  		assert.Equal(t, inst.ContextName, "my_context")
   117  		assert.Equal(t, inst.AuthMode, instance.TwoFactorMail)
   118  	})
   119  
   120  	t.Run("CreateInstanceWithMoreSettings", func(t *testing.T) {
   121  		inst, err := lifecycle.Create(&lifecycle.Options{
   122  			Domain:      "test3.cozycloud.cc",
   123  			UUID:        "XXX",
   124  			OIDCID:      "oidc_42",
   125  			Locale:      "en",
   126  			TOSSigned:   "20151111",
   127  			TOSLatest:   "1.0.0-20151111",
   128  			Timezone:    "Europe/Berlin",
   129  			ContextName: "my_context",
   130  			Email:       "alice@example.com",
   131  			PublicName:  "Alice",
   132  			AuthMode:    "two_factor_mail",
   133  			Passphrase:  "password",
   134  			Settings:    "offer:freemium",
   135  		})
   136  
   137  		assert.NoError(t, err)
   138  		assert.Equal(t, inst.Domain, "test3.cozycloud.cc")
   139  		doc, err := inst.SettingsDocument()
   140  		assert.NoError(t, err)
   141  		assert.Equal(t, "Europe/Berlin", doc.M["tz"].(string))
   142  		assert.Equal(t, "alice@example.com", doc.M["email"].(string))
   143  		assert.Equal(t, "freemium", doc.M["offer"].(string))
   144  		assert.Equal(t, "Alice", doc.M["public_name"].(string))
   145  
   146  		assert.Equal(t, inst.UUID, "XXX")
   147  		assert.Equal(t, inst.OIDCID, "oidc_42")
   148  		assert.Equal(t, inst.Locale, "en")
   149  		assert.Equal(t, inst.TOSSigned, "1.0.0-20151111")
   150  		assert.Equal(t, inst.ContextName, "my_context")
   151  		assert.Equal(t, inst.AuthMode, instance.TwoFactorMail)
   152  	})
   153  
   154  	t.Run("CreateInstanceBadDomain", func(t *testing.T) {
   155  		_, err := lifecycle.Create(&lifecycle.Options{
   156  			Domain: "..",
   157  			Locale: "en",
   158  		})
   159  		assert.Error(t, err, "An error is expected")
   160  
   161  		_, err = lifecycle.Create(&lifecycle.Options{
   162  			Domain: ".",
   163  			Locale: "en",
   164  		})
   165  		assert.Error(t, err, "An error is expected")
   166  
   167  		_, err = lifecycle.Create(&lifecycle.Options{
   168  			Domain: "foo/bar",
   169  			Locale: "en",
   170  		})
   171  		assert.Error(t, err, "An error is expected")
   172  	})
   173  
   174  	t.Run("GetWrongInstance", func(t *testing.T) {
   175  		res, err := lifecycle.GetInstance("no.instance.cozycloud.cc")
   176  		if assert.Error(t, err, "An error is expected") {
   177  			assert.Nil(t, res)
   178  			assert.ErrorIs(t, err, instance.ErrNotFound)
   179  		}
   180  	})
   181  
   182  	t.Run("GetCorrectInstance", func(t *testing.T) {
   183  		instance, err := lifecycle.GetInstance("test.cozycloud.cc")
   184  		if assert.NoError(t, err) {
   185  			assert.NotNil(t, instance)
   186  			assert.Equal(t, instance.Domain, "test.cozycloud.cc")
   187  		}
   188  	})
   189  
   190  	t.Run("InstancehasOAuthSecret", func(t *testing.T) {
   191  		i, err := lifecycle.GetInstance("test.cozycloud.cc")
   192  		if assert.NoError(t, err) {
   193  			assert.NotNil(t, i)
   194  			assert.NotNil(t, i.OAuthSecret)
   195  			assert.Equal(t, len(i.OAuthSecret), instance.OauthSecretLen)
   196  		}
   197  	})
   198  
   199  	t.Run("InstanceHasRootDir", func(t *testing.T) {
   200  		var root vfs.DirDoc
   201  		prefix := getDB(t, "test.cozycloud.cc")
   202  		err := couchdb.GetDoc(prefix, consts.Files, consts.RootDirID, &root)
   203  		if assert.NoError(t, err) {
   204  			assert.Equal(t, root.Fullpath, "/")
   205  		}
   206  	})
   207  
   208  	t.Run("InstanceHasIndexes", func(t *testing.T) {
   209  		var results []*vfs.DirDoc
   210  		prefix := getDB(t, "test.cozycloud.cc")
   211  		req := &couchdb.FindRequest{Selector: mango.Equal("path", "/")}
   212  		err := couchdb.FindDocs(prefix, consts.Files, req, &results)
   213  		assert.NoError(t, err)
   214  		assert.Len(t, results, 1)
   215  	})
   216  
   217  	t.Run("RegisterPassphrase", func(t *testing.T) {
   218  		i, err := lifecycle.GetInstance("test.cozycloud.cc")
   219  		if !assert.NoError(t, err, "cant fetch i") {
   220  			return
   221  		}
   222  		assert.NotNil(t, i)
   223  		assert.NotEmpty(t, i.RegisterToken)
   224  		assert.Len(t, i.RegisterToken, instance.RegisterTokenLen)
   225  		assert.NotEmpty(t, i.OAuthSecret)
   226  		assert.Len(t, i.OAuthSecret, instance.OauthSecretLen)
   227  		assert.NotEmpty(t, i.SessSecret)
   228  		assert.Len(t, i.SessSecret, instance.SessionSecretLen)
   229  
   230  		rtoken := i.RegisterToken
   231  		pass := []byte("passphrase")
   232  		empty := []byte("")
   233  		badtoken := []byte("not-token")
   234  
   235  		err = lifecycle.RegisterPassphrase(i, empty, lifecycle.PassParameters{
   236  			Pass:       pass,
   237  			Iterations: 5000,
   238  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   239  		})
   240  		assert.Error(t, err, "RegisterPassphrase requires token")
   241  
   242  		err = lifecycle.RegisterPassphrase(i, badtoken, lifecycle.PassParameters{
   243  			Pass:       pass,
   244  			Iterations: 5000,
   245  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   246  		})
   247  		assert.Error(t, err, "RegisterPassphrase requires proper token")
   248  
   249  		err = lifecycle.RegisterPassphrase(i, rtoken, lifecycle.PassParameters{
   250  			Pass:       pass,
   251  			Iterations: 5000,
   252  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   253  		})
   254  		assert.NoError(t, err)
   255  
   256  		assert.Empty(t, i.RegisterToken, "RegisterToken has not been removed")
   257  		assert.NotEmpty(t, i.PassphraseHash, "PassphraseHash has not been saved")
   258  
   259  		err = lifecycle.RegisterPassphrase(i, rtoken, lifecycle.PassParameters{
   260  			Pass:       pass,
   261  			Iterations: 5000,
   262  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   263  		})
   264  		assert.Error(t, err, "RegisterPassphrase works only once")
   265  	})
   266  
   267  	t.Run("UpdatePassphrase", func(t *testing.T) {
   268  		i, err := lifecycle.GetInstance("test.cozycloud.cc")
   269  		if !assert.NoError(t, err, "cant fetch i") {
   270  			return
   271  		}
   272  		assert.NotNil(t, i)
   273  		assert.Empty(t, i.RegisterToken)
   274  		assert.NotEmpty(t, i.OAuthSecret)
   275  		assert.Len(t, i.OAuthSecret, instance.OauthSecretLen)
   276  		assert.NotEmpty(t, i.SessSecret)
   277  		assert.Len(t, i.SessSecret, instance.SessionSecretLen)
   278  
   279  		oldHash := i.PassphraseHash
   280  		oldSecret := i.SessSecret
   281  
   282  		currentPass := []byte("passphrase")
   283  		newPass := []byte("new-passphrase")
   284  		badPass := []byte("not-passphrase")
   285  		empty := []byte("")
   286  
   287  		params := lifecycle.PassParameters{
   288  			Pass:       newPass,
   289  			Iterations: 5000,
   290  		}
   291  		err = lifecycle.UpdatePassphrase(i, empty, "", nil, params)
   292  		assert.Error(t, err, "UpdatePassphrase requires the current passphrase")
   293  
   294  		err = lifecycle.UpdatePassphrase(i, badPass, "", nil, params)
   295  		assert.Error(t, err, "UpdatePassphrase requires the current passphrase")
   296  
   297  		err = lifecycle.UpdatePassphrase(i, currentPass, "", nil, params)
   298  		assert.NoError(t, err)
   299  
   300  		assert.NotEmpty(t, i.PassphraseHash, "PassphraseHash has not been saved")
   301  		assert.NotEqual(t, oldHash, i.PassphraseHash)
   302  		assert.NotEqual(t, oldSecret, i.SessSecret)
   303  
   304  		settings, err := settings.Get(i)
   305  		assert.NoError(t, err)
   306  		assert.Equal(t, 5000, settings.PassphraseKdfIterations)
   307  		assert.Equal(t, 0, settings.PassphraseKdf)
   308  	})
   309  
   310  	t.Run("RequestPassphraseReset", func(t *testing.T) {
   311  		in, err := lifecycle.Create(&lifecycle.Options{
   312  			Domain: "test.cozycloud.cc.pass_reset",
   313  			Locale: "en",
   314  		})
   315  		require.NoError(t, err)
   316  
   317  		err = lifecycle.RequestPassphraseReset(in, "")
   318  		require.NoError(t, err)
   319  
   320  		// token should not have been generated since we have not set a passphrase
   321  		// yet
   322  		require.Nil(t, in.PassphraseResetToken)
   323  
   324  		err = lifecycle.RegisterPassphrase(in, in.RegisterToken, lifecycle.PassParameters{
   325  			Pass:       []byte("MyPassphrase"),
   326  			Iterations: 5000,
   327  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   328  		})
   329  		require.NoError(t, err)
   330  
   331  		err = lifecycle.RequestPassphraseReset(in, "")
   332  		require.NoError(t, err)
   333  
   334  		regToken := in.PassphraseResetToken
   335  		regTime := in.PassphraseResetTime
   336  		assert.NotNil(t, in.PassphraseResetToken)
   337  		assert.True(t, !in.PassphraseResetTime.Before(time.Now().UTC()))
   338  
   339  		err = lifecycle.RequestPassphraseReset(in, "")
   340  		assert.Equal(t, instance.ErrResetAlreadyRequested, err)
   341  		assert.EqualValues(t, regToken, in.PassphraseResetToken)
   342  		assert.EqualValues(t, regTime, in.PassphraseResetTime)
   343  	})
   344  
   345  	t.Run("PassphraseRenew", func(t *testing.T) {
   346  		in, err := lifecycle.Create(&lifecycle.Options{
   347  			Domain: "test.cozycloud.cc.pass_renew",
   348  			Locale: "en",
   349  		})
   350  		require.NoError(t, err)
   351  
   352  		err = lifecycle.RegisterPassphrase(in, in.RegisterToken, lifecycle.PassParameters{
   353  			Pass:       []byte("MyPassphrase"),
   354  			Iterations: 5000,
   355  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   356  		})
   357  		require.NoError(t, err)
   358  
   359  		passHash := in.PassphraseHash
   360  		err = lifecycle.PassphraseRenew(in, nil, lifecycle.PassParameters{
   361  			Pass:       []byte("NewPass"),
   362  			Iterations: 5000,
   363  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   364  		})
   365  		require.Error(t, err)
   366  
   367  		err = lifecycle.RequestPassphraseReset(in, "")
   368  		require.NoError(t, err)
   369  
   370  		err = lifecycle.PassphraseRenew(in, []byte("token"), lifecycle.PassParameters{
   371  			Pass:       []byte("NewPass"),
   372  			Iterations: 5000,
   373  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   374  		})
   375  		require.Error(t, err)
   376  
   377  		err = lifecycle.PassphraseRenew(in, in.PassphraseResetToken, lifecycle.PassParameters{
   378  			Pass:       []byte("NewPass"),
   379  			Iterations: 5000,
   380  			Key:        "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=",
   381  		})
   382  		require.NoError(t, err)
   383  
   384  		assert.False(t, bytes.Equal(passHash, in.PassphraseHash))
   385  	})
   386  
   387  	t.Run("InstanceNoDuplicate", func(t *testing.T) {
   388  		_, err := lifecycle.Create(&lifecycle.Options{
   389  			Domain: "test.cozycloud.cc.duplicate",
   390  			Locale: "en",
   391  		})
   392  		require.NoError(t, err)
   393  
   394  		i, err := lifecycle.Create(&lifecycle.Options{
   395  			Domain: "test.cozycloud.cc.duplicate",
   396  			Locale: "en",
   397  		})
   398  		if assert.Error(t, err, "Should not be possible to create duplicate") {
   399  			assert.Nil(t, i)
   400  			assert.ErrorIs(t, err, instance.ErrExists)
   401  		}
   402  	})
   403  
   404  	t.Run("CheckPassphrase", func(t *testing.T) {
   405  		inst, err := lifecycle.GetInstance("test.cozycloud.cc")
   406  		if !assert.NoError(t, err, "cant fetch instance") {
   407  			return
   408  		}
   409  
   410  		assert.Empty(t, inst.RegisterToken, "changes have been saved in db")
   411  		assert.NotEmpty(t, inst.PassphraseHash, "changes have been saved in db")
   412  
   413  		err = instance.CheckPassphrase(inst, []byte("not-passphrase"))
   414  		assert.Error(t, err)
   415  
   416  		err = instance.CheckPassphrase(inst, []byte("new-passphrase"))
   417  		assert.NoError(t, err)
   418  	})
   419  
   420  	t.Run("CheckTOSNotSigned", func(t *testing.T) {
   421  		now := time.Now()
   422  		i, err := lifecycle.Create(&lifecycle.Options{
   423  			Domain:    "tos.test.cozycloud.cc",
   424  			Locale:    "en",
   425  			TOSSigned: "1.0.0-" + now.Format("20060102"),
   426  		})
   427  		require.NoError(t, err)
   428  
   429  		notSigned, deadline := i.CheckTOSNotSignedAndDeadline()
   430  		assert.Empty(t, i.TOSLatest)
   431  		assert.False(t, notSigned)
   432  		assert.Equal(t, instance.TOSNone, deadline)
   433  
   434  		err = lifecycle.Patch(i, &lifecycle.Options{
   435  			TOSLatest: "1.0.1-" + now.Format("20060102"),
   436  		})
   437  		require.NoError(t, err)
   438  
   439  		notSigned, deadline = i.CheckTOSNotSignedAndDeadline()
   440  		assert.Empty(t, i.TOSLatest)
   441  		assert.False(t, notSigned)
   442  		assert.Equal(t, instance.TOSNone, deadline)
   443  
   444  		err = lifecycle.Patch(i, &lifecycle.Options{
   445  			TOSLatest: "2.0.1-" + now.Add(40*24*time.Hour).Format("20060102"),
   446  		})
   447  		require.NoError(t, err)
   448  
   449  		notSigned, deadline = i.CheckTOSNotSignedAndDeadline()
   450  		assert.NotEmpty(t, i.TOSLatest)
   451  		assert.True(t, notSigned)
   452  		assert.Equal(t, instance.TOSNone, deadline)
   453  
   454  		err = lifecycle.Patch(i, &lifecycle.Options{
   455  			TOSLatest: "2.0.1-" + now.Add(10*24*time.Hour).Format("20060102"),
   456  		})
   457  		require.NoError(t, err)
   458  
   459  		notSigned, deadline = i.CheckTOSNotSignedAndDeadline()
   460  		assert.NotEmpty(t, i.TOSLatest)
   461  		assert.True(t, notSigned)
   462  		assert.Equal(t, instance.TOSWarning, deadline)
   463  
   464  		err = lifecycle.Patch(i, &lifecycle.Options{
   465  			TOSLatest: "2.0.1-" + now.Format("20060102"),
   466  		})
   467  		require.NoError(t, err)
   468  
   469  		notSigned, deadline = i.CheckTOSNotSignedAndDeadline()
   470  		assert.NotEmpty(t, i.TOSLatest)
   471  		assert.True(t, notSigned)
   472  		assert.Equal(t, instance.TOSBlocked, deadline)
   473  
   474  		err = lifecycle.Patch(i, &lifecycle.Options{
   475  			TOSSigned: "2.0.1-" + now.Format("20060102"),
   476  		})
   477  		require.NoError(t, err)
   478  
   479  		notSigned, deadline = i.CheckTOSNotSignedAndDeadline()
   480  		assert.Empty(t, i.TOSLatest)
   481  		assert.False(t, notSigned)
   482  		assert.Equal(t, instance.TOSNone, deadline)
   483  	})
   484  
   485  	t.Run("InstanceDestroy", func(t *testing.T) {
   486  		_ = lifecycle.Destroy("test.cozycloud.cc")
   487  
   488  		_, err := lifecycle.Create(&lifecycle.Options{
   489  			Domain: "test.cozycloud.cc",
   490  			Locale: "en",
   491  		})
   492  		require.NoError(t, err)
   493  
   494  		err = lifecycle.Destroy("test.cozycloud.cc")
   495  		assert.NoError(t, err)
   496  
   497  		err = lifecycle.Destroy("test.cozycloud.cc")
   498  		if assert.Error(t, err) {
   499  			assert.Equal(t, instance.ErrNotFound, err)
   500  		}
   501  	})
   502  }
   503  
   504  func cleanInstance() {
   505  	_ = lifecycle.Destroy("test.cozycloud.cc")
   506  	_ = lifecycle.Destroy("test2.cozycloud.cc")
   507  	_ = lifecycle.Destroy("test3.cozycloud.cc")
   508  	_ = lifecycle.Destroy("test.cozycloud.cc.pass_reset")
   509  	_ = lifecycle.Destroy("test.cozycloud.cc.pass_renew")
   510  	_ = lifecycle.Destroy("test.cozycloud.cc.duplicate")
   511  	_ = lifecycle.Destroy("tos.test.cozycloud.cc")
   512  }
   513  
   514  func getDB(t *testing.T, domain string) prefixer.Prefixer {
   515  	instance, err := lifecycle.GetInstance(domain)
   516  	if !assert.NoError(t, err, "Should get instance %v", domain) {
   517  		t.FailNow()
   518  	}
   519  	return instance
   520  }